package org.sifappscanplugin.publisher;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ProtocolException;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.auth.KerberosSchemeFactory;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.AutoRetryHttpClient;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
//import org.sifappscanplugin.publisher.ASEAuthenticator;
//import org.sif.core.authentication.Authenticator;
import org.sif.core.authentication.JCIFSNTLMSchemeFactory;
import org.sif.core.util.TextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class connects to an AppScan Enterprise (ASE) instance to perform
* various functions that are not supported by the AppScan Source for Security
* Edition (ASSE) CLI. It relies on the AppScan REST services as well as the web
* interface. NTLM and Jazz authentication are supported. NTLMv2 is even
* supported, even though ASSE itself does not currently support it. This class
* has been tested with ASE 8.6.
*
* @author David Anderson
* @date February, 2013
*
*/
public class ASERestServicesClient
{
final Logger logger = LoggerFactory.getLogger( ASERestServicesClient.class );
PrintWriter writer;
CloseableHttpClient httpClient;
HttpContext httpContext;
// Authenticator authenticator;
String location;
XPath xpath;
String aseUri = "appscanreporting.enterprise.irs.gov/ase";
boolean loggingInit = false;
private String domain;
private String user;
private String password;
private boolean acceptSsl;
private final static int FOLDER_ID_ROOT = 1;
static boolean testMode = false; // Reads XML files from local file-system
// rather than from an ASE server.
private void setupLogging()
{
try
{
writer = new PrintWriter( "C:\\Plugin_Log.log", "UTF-8" );
loggingInit = true;
writer.println();
}
catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// public ASERestServicesClient()
// {
// // Initialization of XPath utilities
// XPathFactory factory = XPathFactory.newInstance();
// xpath = factory.newXPath();
// xpath.setNamespaceContext( _nsContext );
//
// if ( !loggingInit )
// {
// // Disabled to diagnose errors with 9.0.1
// // setupLogging();
// }
//
// }
public ASERestServicesClient(String location, String domain, String user, String password,
boolean acceptSsl)
{
// Initialization of XPath utilities
XPathFactory factory = XPathFactory.newInstance();
xpath = factory.newXPath();
xpath.setNamespaceContext( _nsContext );
// authenticator = new ASEAuthenticator( location, domain, user, password, acceptSsl );
// httpClient = authenticator.getHttpClient();
// this.location = location;
this.location = location;
this.domain = domain;
this.user = user;
this.password = password;
this.acceptSsl = acceptSsl;
}
// public ASERestServicesClient(String location)
// {
// this();
// this.location = location;
// }
/**
* Establish an authenticated HTTP connection to the location using Jazz authentication, which
* is a simple POST-based userid+password authentication scheme implemented by Jazz Team Server.
* This method implements preemptive authentication using the provided credentials, which is not
* a best practice for server to server communication. Properly managed client certificates
* should be used instead.
* FIXME: I'm not sure about the above, and it may have changed in more recent versions of ASE.
* We should document here the sequence of messages expected for a successful authentication.
*/
public void login() throws ClientProtocolException, IOException
{
// POST to /services/login
// Content-Type: application/x-www-form-urlencoded
// userid: element containing the user's login id.
// password: element containing the user's password.
// Create a local instance of cookie store
CookieStore cookieStore = new BasicCookieStore();
httpContext = new BasicHttpContext();
try
{
// We need the HTTP client to automatically follow redirects for POSTs since ASE will
// routinely respond with 302 for a browser compatibility check.
HttpClientBuilder builder = HttpClients.custom();
// builder.setRedirectStrategy(new LaxRedirectStrategy()
// {
// @Override
// public boolean isRedirected(HttpRequest request,
// HttpResponse response,
// HttpContext context) throws ProtocolException
// {
// logger.debug( "Redirecting request: " + request );
// return super.isRedirected( request, response, context );
// }
// });
builder.setServiceUnavailableRetryStrategy(new ServiceUnavailableRetryStrategy() {
@Override
public boolean retryRequest(
final HttpResponse response, final int executionCount, final HttpContext context)
{
logger.debug( "Checking for retry condition" );
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 302 && executionCount < 2)
logger.error( "Retrying request due to 302 redirect response" );
return statusCode == 302 && executionCount < 2;
}
@Override
public long getRetryInterval() {
return 0;
}
});
// HttpHost proxy = new HttpHost("localhost", 5555);
// DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
// builder.setRoutePlanner( routePlanner );
if ( isAcceptSsl() )
{
logger.debug( "Initializing https client that ignores SSL certificate mismatches" );
Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.NTLM, new JCIFSNTLMSchemeFactory())
.register(AuthSchemes.BASIC, new BasicSchemeFactory())
.register(AuthSchemes.DIGEST, new DigestSchemeFactory())
.register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
.register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
.build();
builder.setSSLSocketFactory(getSSLSocketFactoryTrustingAllCerts());
builder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
builder.setDefaultCookieStore(cookieStore);
httpClient = builder.build();
}
else
{
logger.debug( "Initializing default https client" );
httpClient = builder.build();
}
}
catch (KeyManagementException e)
{
logger.error( "Key management", e );
}
catch (NoSuchAlgorithmException e)
{
logger.error( "No such algorithm", e );
}
catch (KeyStoreException e)
{
logger.error( "Problem with keystore", e );
}
// String localHostname;
// try
// {
// localHostname = InetAddress.getLocalHost().getCanonicalHostName();
// }
// catch (UnknownHostException e1)
// {
// logger.error("Cannot get local hostname", e1);
// localHostname = "localhost";
// }
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new NTCredentials(getUser(), getPassword(), getLocation(), getDomain()));
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credentialsProvider);
HttpPost request = new HttpPost( getLocation() + "/services/login" );
// Setup the request parameters
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>( 2 );
nameValuePairs.add( new BasicNameValuePair( "userid", getUser() ) );
nameValuePairs.add( new BasicNameValuePair( "password", getPassword() ) );
request.setEntity( new UrlEncodedFormEntity( nameValuePairs ) );
CloseableHttpResponse response = null;
try
{
// Make the request
// outStream.println("New HTTP Request: " +
// request.getRequestLine());
response = send(request, httpContext);
logger.debug( response.getStatusLine().toString() );
// outStream.println("Response: " + response.getStatusLine());
if ( response != null )
{
HttpEntity responseEntity = response.getEntity();
logger.debug( response.getStatusLine().toString() );
if ( responseEntity != null )
{
logger.debug( "Response content length: " + responseEntity.getContentLength() );
}
String jsonResultString = EntityUtils.toString( responseEntity );
// logger.debug( "Response: " + jsonResultString );
EntityUtils.consume( responseEntity );
}
}
catch (ClientProtocolException e)
{
logger.error( "Illegal protocol", e );
}
catch (IOException e)
{
logger.error( "Error sending request", e );
}
finally
{
if ( response != null )
{
response.close();
}
}
}
/**
* Get an SSL connection factory that trusts all certificates and allows hostname mismatches.
* WARNING: This is NOT recommended for a production system as it exposes the application to
* man in the middle attacks. In production, the following should be done:
*
* 1. The public key should be imported to the appropriate truststore on the client machine,
* as in this example command:
*
* keytool -import -alias appscan -file mycert.cer -keystore cacertspersonal
*
* Refer to the documentation for your release of AppScan Source to locate the truststore.
*
* 2. The certificate should be created using the correct subjectAlternativeName
* (see http://tools.ietf.org/search/rfc6125) so that it matches the host.
*
* For example:
*
* <pre>
* keytool -genkeypair \
* -keystore keystore.jks \
* -dname "CN=example.com, OU=Sun Java System Application Server, O=Sun Microsystems, L=Santa Clara, ST=California, C=US" \
* -keypass changeit \
* -storepass changeit \
* -keyalg RSA \
* -keysize 2048 \
* -alias example \
* -ext SAN=DNS:example.com \
* -validity 9999
* </pre>
* @return
* @throws KeyManagementException
* @throws NoSuchAlgorithmException
* @throws KeyStoreException
*/
protected SSLConnectionSocketFactory getSSLSocketFactoryTrustingAllCerts() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException
{
SSLConnectionSocketFactory factory = null;
SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
// factory = new SSLConnectionSocketFactory(builder.build());
factory = new SSLConnectionSocketFactory(builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
return factory;
}
public CloseableHttpResponse send(HttpUriRequest request, HttpContext httpContext) throws ClientProtocolException, IOException
{
return httpClient.execute( request, httpContext );
}
/**
* Get the folder id that corresponds to the given folder path on the ASE instance.
*
* @param folder
* @return
*/
public Integer getFolderIdService(String folder)
{
Integer id = null;
try
{
if (!testMode)
{
login();
}
id = getFolderId(folder);
}
catch (ClientProtocolException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
}
catch (SAXException e)
{
e.printStackTrace();
}
// finally
// {
// if (!testMode)
// {
// authenticator.shutdown();
// }
// }
return id;
}
/**
* Create a new folder at the given folder path on the ASE instance.
*
* @param folder
* @return
*/
public boolean viewFolderService(String folder)
{
boolean result = false;
try
{
if (!testMode)
{
login();
}
result = viewFolder(folder);
}
catch (ClientProtocolException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
}
catch (SAXException e)
{
e.printStackTrace();
}
catch (ASEClientException e)
{
e.printStackTrace();
}
// finally
// {
// if (!testMode)
// {
// authenticator.shutdown();
// }
// }
return result;
}
/**
* Create a new folder at the given folder path on the ASE instance.
*
* @param folder
* @return
*/
public boolean createFolderService(String folder, String description, String contact)
{
boolean result = false;
try
{
if (!testMode)
{
login();
}
result = createFolder(folder, description, contact);
}
catch (ClientProtocolException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
}
catch (SAXException e)
{
e.printStackTrace();
}
catch (ASEClientException e)
{
e.printStackTrace();
}
// finally
// {
// if (!testMode)
// {
// authenticator.shutdown();
// }
// }
return result;
}
public String getLocation()
{
return location;
}
public String getDomain()
{
return domain;
}
public String getUser()
{
return user;
}
public String getPassword()
{
return password;
}
public boolean isAcceptSsl()
{
return acceptSsl;
}
/**
* @deprecated
* @param parentId
* @param folderName
* @return
*/
public int checkForFolderAtParentId(int parentId, String folderName)
{
// FIXME: Hack!
int folderId = -99; // a return value of -99 means the folder was not
// found
String folderCheckURI = this.location + "/services/folders/" + parentId + "/folders";
logger.debug( "URL: " + folderCheckURI );
Document response = null;
try
{
response = sendRESTRequest( folderCheckURI, "" );
}
catch (ParserConfigurationException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (IOException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch (SAXException e1)
{
// TODO Auto-generated catch block
e1.printStackTrace();
}
if ( response != null )
{
try
{
// logger.info("Response is not null.");
checkForError( response );
}
catch (ASEClientException e)
{
e.printStackTrace();
}
}
if ( response != null )
{
try
{
Object folderResult = xpath.evaluate( "/ase:folder | /ase:folders/ase:folder",
response, XPathConstants.NODESET );
NodeList folderNodes = (NodeList) folderResult;
for ( int i = 0; i < folderNodes.getLength(); i++ )
{
logger.debug( "Looking for folder in a new node..." );
Element folderNode = (Element) folderNodes.item( i );
if ( xpath.evaluate( "ase:name/text()", folderNode, XPathConstants.STRING )
.toString().equals( folderName ) )
{
logger.debug( "Folder element name: " + folderName );
String folderIdString = (String) xpath.evaluate( "ase:id/text()",
folderNode, XPathConstants.STRING );
logger.debug( "Folder element id: " + folderIdString );
folderId = Integer.parseInt( folderIdString );
logger.debug( "Found folder: " + folderName + "!" );
logger.debug( "Folder ID: " + folderId );
break;
}
}
logger.debug( "Finished with that node.." );
logger.debug( "response start--------------" );
printDocument( response );
logger.debug( "response end----------------" );
}
catch (XPathExpressionException e)
{
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (TransformerException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return folderId;
}
/**
* @deprecated
* @param parentId
* @param folder
* @param description
* @param contact
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
* @throws ASEClientException
*/
public void createFolder(int parentId, String folder, String description, String contact)
throws ParserConfigurationException, SAXException, IOException, ASEClientException
{
ASElogin();
logger.debug( "Sending Create Folder Post..." );
sendCreateFolderPost( folder, parentId, description, contact, 0 );
ASElogout();
}
/**
* @deprecated
* @throws ClientProtocolException
* @throws IOException
*/
private void ASElogin() throws ClientProtocolException, IOException
{
login();
logger.info( "Logged in." );
}
/**
* @deprecated
*/
private void ASElogout()
{
logger.info( "Logging out..." );
// authenticator.shutdown();
}
/**
* @deprecated
* @param parentId
* @param folders
* @param description
* @param contact
* @return
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
* @throws ASEClientException
*/
public int createFolderStructure(int parentId, String folders[], String description,
String contact) throws ParserConfigurationException, SAXException, IOException,
ASEClientException
{
logger.debug( "" );
logger.debug( "Called createFolderStructure..." );
int folderId = -99;
try
{
ASElogin();
for ( int currentFolder = 0; currentFolder < folders.length; currentFolder++ )
{
logger.debug( "" );
logger.debug( "createFolderService params: " + parentId + ", "
+ folders[currentFolder] + ", " + description + ", " + contact + "." );
// Check if the folder we are looking for exists at the
// parentID. If not, -99 will be returned.
folderId = checkForFolderAtParentId( parentId, folders[currentFolder] );
logger.debug( "Folder ID for " + folders[currentFolder] + " at parentId "
+ parentId + "is: " + folderId );
// Our folder was found
if ( folderId != -99 )
{
logger.debug( "Folder " + folders[currentFolder]
+ " was found with folderId " + folderId + " and will not be created." );
parentId = folderId;
// Folder exists, so we keep moving..
continue;
}
else
{
ASElogout();
logger.debug( "Calling createFolder: " + parentId + ": "
+ folders[currentFolder] );
createFolder( parentId, folders[currentFolder], "", "" );
ASElogin();
logger.debug( "Confirming we actually created the folder.." );
folderId = checkForFolderAtParentId( parentId, folders[currentFolder] );
logger.debug( "FolderId found: " + folderId );
if ( folderId == -99 )
{
// This should probably actually throw an exception,
// since it means something didn't work
logger.debug( "Folder was not actually created..." );
logger.debug( "" );
}
else
{
logger.debug( "New Folder ID: " + folderId );
logger.debug( "Folder creation confirmed!" );
parentId = folderId;
}
}
}
}
finally
{
// if (!testMode)
// {
ASElogout();
logger.debug( "" );
// outputStream.flush();
// outputStream.close();
// }
}
logger.debug( "Returning folderId: " + folderId );
return folderId;
}
private String translateJobFolderToASEFolder(String folder, ASERepository aseRepository)
{
String aseFolder = null;
aseFolder = TextUtil.trimLeft( folder, "/" );
aseFolder = aseRepository.getRootFolder() + "/" + aseFolder;
aseFolder.replaceAll( "\\\\", "/" );
aseFolder = TextUtil.trim( aseFolder, "/" );
return aseFolder;
}
Integer getFolderId(String folder) throws ParserConfigurationException, SAXException,
IOException
{
Integer id = null;
ASERepository aseRepository = new ASERepository();
mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() );
folder = translateJobFolderToASEFolder(folder, aseRepository);
id = aseRepository.getMap().get( folder );
return id;
}
class ASERepository
{
private Map<String, Integer> map = new Hashtable<String, Integer>();
private String rootFolder;
public int size()
{
return map.size();
}
public boolean containsKey(Object key)
{
return map.containsKey( key );
}
public Integer get(Object key)
{
return map.get( key );
}
public Integer put(String key, Integer value)
{
return map.put( key, value );
}
public Map<String, Integer> getMap()
{
return map;
}
public void setMap(Map<String, Integer> map)
{
this.map = map;
}
public String getRootFolder()
{
return rootFolder;
}
public void setRootFolder(String rootFolder)
{
this.rootFolder = rootFolder;
}
}
ASERepository mapFolders(String rootFolderUrl, ASERepository aseRepository,
Stack<String> pathElements) throws ParserConfigurationException, SAXException,
IOException
{
logger.debug( "Mapping ASE folders structure at " + rootFolderUrl );
Document response = null;
if ( testMode )
{
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware( true ); // never forget this!
File subfoldersFile = translateUrlToFilesystemPath( rootFolderUrl );
DocumentBuilder builder;
builder = domFactory.newDocumentBuilder();
if ( subfoldersFile.exists() )
response = builder.parse( subfoldersFile );
else
logger.debug( "WARNING: Missing node.xml at " + subfoldersFile );
}
else
{
response = sendRESTRequest( rootFolderUrl, "" );
if ( response != null )
{
try
{
checkForError( response );
}
catch (ASEClientException e)
{
e.printStackTrace();
}
}
}
if ( response != null )
{
try
{
String folderName = null;
Integer folderId = null;
Object folderResult = xpath.evaluate( "/ase:folder | /ase:folders/ase:folder",
response, XPathConstants.NODESET );
NodeList folderNodes = (NodeList) folderResult;
for ( int i = 0; i < folderNodes.getLength(); i++ )
{
Element folderNode = (Element) folderNodes.item( i );
folderName = (String) xpath.evaluate( "ase:name/text()", folderNode,
XPathConstants.STRING );
// logger.debug( "Folder element name: " + folderName );
String folderIdString = (String) xpath.evaluate( "ase:id/text()", folderNode,
XPathConstants.STRING );
// logger.debug( "Folder element id: " + folderIdString );
folderId = Integer.parseInt( folderIdString );
if ( folderName != null && folderId != null )
{
if ( pathElements.size() >= 32 )
{
logger.debug( "Stopping the mapping of a folder branch that exceeds the depth limit of 32." );
continue;
}
pathElements.push( folderName );
String path = TextUtil.convertListToString( pathElements, "/" );
// logger.debug( "#### Mapping folder path " + path + " with id "
// + folderId );
aseRepository.put( path, folderId );
// Remember the root folder
if (folderId == FOLDER_ID_ROOT)
{
aseRepository.setRootFolder( TextUtil.trim( path, "/") );
}
String subfoldersUrl = (String) xpath.evaluate( "ase:folders/@href",
folderNode, XPathConstants.STRING );
// Recurse into a sub-folder
mapFolders( subfoldersUrl, aseRepository, pathElements );
pathElements.pop();
}
}
}
catch (XPathExpressionException e)
{
e.printStackTrace();
}
}
return aseRepository;
}
private File translateUrlToFilesystemPath(String url)
{
logger.debug( "Translating URL " + url );
File file = null;
int doubleSlashPos = url.indexOf( "//" );
int slashPos = url.indexOf( "/", doubleSlashPos + 2 );
// Skip the following "/ase/services/"
String context = "/ase/services/";
if ( url.substring( slashPos, slashPos + context.length() ).equals( context ) )
file = new File( "test/" + url.substring( slashPos + context.length() ) + "/node.xml" );
else
throw new RuntimeException( "Cannot find XML docuemnt for URL " + url + " at file: "
+ file );
return file;
}
/**
*
* @param service
* - URL of the REST service call
* @param data
* - (optional) Content of the POST data (XML). When empty, using
* GET.
* @return Response (XML)
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws Exception
*/
private Document sendRESTRequest(String service, String data)
throws ParserConfigurationException, IOException, SAXException
{
logger.debug( "Sending REST request to " + service );
// outStream.println("Sending REST request to " + service);
Document document = null;
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
domFactory.setNamespaceAware( true ); // never forget this!
DocumentBuilder builder = domFactory.newDocumentBuilder();
HttpUriRequest request;
if ( data != null && data.length() > 0 )
{
HttpPost postRequest = new HttpPost( service );
postRequest.addHeader( "Content-Type", "application/x-www-form-urlencoded" );
StringEntity entity = new StringEntity( data );
postRequest.setEntity( entity );
request = postRequest;
}
else
{
request = new HttpGet( service );
}
HttpResponse response = null;
try
{
// Make the request
response = send( request, httpContext );
// response = httpClient.execute( request, httpContext );
logger.debug( "REST response: " + response.getStatusLine() );
logger.debug( response.getStatusLine().toString() );
}
catch (ClientProtocolException e)
{
logger.debug( "Illegal protocol" + e.getMessage() );
}
catch (IOException e)
{
logger.debug( "Error sending request" + e.getMessage() );
}
if ( response != null )
{
HttpEntity responseEntity = response.getEntity();
logger.debug( response.getStatusLine().toString() );
if ( responseEntity != null )
{
logger.debug( "Response content length: "
+ responseEntity.getContentLength() );
}
document = builder.parse( responseEntity.getContent() );
}
return document;
}
// FIXME
//
// This method does not seem to accurately return whether or not the folder
// is created.
// This method also does not accurately report if the path begins with ASE
// FIXME: This method should be checked for logic issues
public boolean createFolder(String folder, String description, String contact)
throws ParserConfigurationException, SAXException, IOException, ASEClientException
{
logger.debug( "Creating ASE folder: " + folder );
boolean result = false;
String parentFolder = null;
if ( folder != null && folder.length() > 0 )
{
ASERepository aseRepository = new ASERepository();
mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() );
folder = translateJobFolderToASEFolder(folder, aseRepository);
String[] pathElements = folder.split( "/" );
if ( pathElements.length > 0 )
{
logger.debug( "Creating ASE folder with path elements: " + Arrays.asList(pathElements) );
ArrayList<String> ancestorList = new ArrayList<String>();
// Create each ancestor directory path that is not in 'folders'
for ( int i = 0; i < pathElements.length; i++ )
{
String ancestorPathElement = pathElements[i];
parentFolder = TextUtil.convertListToString( ancestorList, "/" );
ancestorList.add( ancestorPathElement );
String thisFolder = TextUtil.convertListToString( ancestorList, "/" );
logger.debug( "Ensuring existence of folder " + thisFolder + " under " + parentFolder);
// The root folder always exists, so we do not try to create it.
if (i > 0)
{
// For each 'parentFolder' check if it is in
// 'folders'
Integer parentFolderId = aseRepository.get( parentFolder );
Integer thisFolderId = aseRepository.get( thisFolder );
if ( thisFolderId == null)
{
logger.debug( "Creating ASE folder under " + parentFolder
+ " with name " + ancestorPathElement );
// outStream.println("Creating ASE folder in " +
// parentFolder + " with name " +
// ancestorPathElement);
sendCreateFolderPost( ancestorPathElement,
parentFolderId.intValue(), description, contact, 0 );
// Add the folder we just created to our map of all folders.
// FIXME: Maybe we should get the folder id back from the
// sendCreateFolderPost() call so we can just add it to the map.
mapFolders( this.location + "/services/folders", aseRepository,
new Stack<String>() );
}
}
}
result = true;
}
}
return result;
}
// numTimesRequested helps us keep track of how many times we've sent this
// request. From outside this method, it should always be set to 0.
private void sendCreateFolderPost(String name, int parentFolderId, String description,
String contact, int numTimesRequested) throws ParserConfigurationException,
SAXException, IOException, ASEClientException
{
if ( name != null )
{
if ( description == null )
description = "";
if ( contact == null )
contact = "";
HttpPost request = new HttpPost( this.location + "/AddFolder.aspx?fid="
+ parentFolderId );
logger.debug( "HTTP Request: " + request.toString() );
BasicNameValuePair[] params =
{
new BasicNameValuePair( "__LASTFOCUS", "" ),
new BasicNameValuePair( "__EVENTTARGET",
"ctl00$ctl00$ctl00$MCH$RCH$SaveApplyCancel$SaveButton" ),
new BasicNameValuePair( "__EVENTARGUMENT", "" ),
new BasicNameValuePair(
"ctl00$ctl00$ctl00$MCH$RCH$PageContentPlaceHolder$Identification$Name",
name ),
new BasicNameValuePair(
"ctl00$ctl00$ctl00$MCH$RCH$PageContentPlaceHolder$Identification$Description",
description ),
new BasicNameValuePair(
"ctl00$ctl00$ctl00$MCH$RCH$PageContentPlaceHolder$Identification$Contact",
contact ),
new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$ScrollerLeft=ScrollerLeft",
"ScrollerLeft" ),
new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$ScrollerTop=ScrollerTop",
"ScrollerTop" ),
new BasicNameValuePair( "ctl00$ctl00$ctl00$MCH$RCH$SaveApplyCancel$SaveButton",
"Create" ),
};
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(
Arrays.asList( params ) );
// urlEncodedFormEntity.setContentEncoding(HTTP.UTF_8);
request.setEntity( urlEncodedFormEntity );
request.addHeader( "User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0");
request.addHeader( "Referer", this.location + "/AddFolder.aspx?fid=" + parentFolderId);
Header[] headers = request.getAllHeaders();
logger.debug( "Request Headers:" );
for ( int p = 0; p < headers.length; p++ )
{
logger.debug( headers[p].toString() );
}
logger.debug( "End of headers!" );
HttpResponse response = null;
// Make the request
response = send( request, httpContext );
// response = httpClient.execute( request, httpContext );
logger.debug( response.getStatusLine().toString() );
logger.debug( "HTTP Response: " + response.getStatusLine() );
// Read the full response and drop it.
HttpEntity responseEntity = response.getEntity();
InputStream responseIn = responseEntity.getContent();
logger.debug( "Response Content: " );
BufferedReader reader = new BufferedReader( new InputStreamReader( responseIn ) );
String line;
while ( ( line = reader.readLine() ) != null )
{
logger.debug( line );
}
EntityUtils.consume( responseEntity );
// Let the caller know if it succeeded
int responseCode = response.getStatusLine().getStatusCode();
logger.debug( "Response Code: " + responseCode );
headers = response.getAllHeaders();
logger.debug( "Response Headers:" );
for ( int p = 0; p < headers.length; p++ )
{
logger.debug( headers[p].toString() );
}
logger.debug( "End of headers!" );
if (responseCode < 200 || responseCode >= 400)
throw new ASEClientException("Failed to create folder on the ASE instance with response code " + responseCode);
if ( responseCode != 200 )
{
// This is to get around the check browser issue. ASE often
// responds with a 302 redirect to CheckBrowserVersion.aspx on
// the first request
// But the second request goes through..
if ( responseCode == 302 )
{
boolean incompatibleBrowser = false;
for ( int p = 0; p < headers.length; p++ )
{
if ( headers[p].getValue().contains( "CheckBrowserVersion.aspx" ) )
{
logger.debug( "We're being redirected to due to an incompatible browser.. " );
incompatibleBrowser = true;
// This check makes sure we only re-send the request
// once...
if ( numTimesRequested == 0 )
{
// This should eventually be changed, since it's
// really inefficient to re-run this entire
// method.. we should just make the request and
// parse the response.
sendCreateFolderPost( name, parentFolderId, description, contact,
numTimesRequested++ );
logger.debug( "We'll try the request one more time.." );
}
else
{
logger.debug( "Already tried to request twice, so we won't do it again.." );
}
}
}
if ( !incompatibleBrowser )
logger.debug( "We're being redirected, but not due to an incompatible brower.. this is probably normal." );
}
else
throw new ASEClientException(
"Failed to create folder on the ASE instance with response code "
+ responseCode );
}
}
}
public boolean viewFolder(String folder) throws ParserConfigurationException, SAXException,
IOException, ASEClientException
{
boolean result = false;
if ( folder != null && folder.length() > 0 )
{
folder.replaceAll( "\\\\", "/" );
folder = TextUtil.trim( folder, "/" );
String[] pathElements = folder.split( "/" );
if ( pathElements.length > 1 )
{
ASERepository aseRepository = new ASERepository();
mapFolders( this.location + "/services/folders", aseRepository, new Stack<String>() );
// Verify that pathElements[0].equals("ASE");
if ( pathElements[0].equals( "ASE" ) )
{
Integer folderId = aseRepository.get( folder );
if ( folderId != null )
{
logger.debug( "Getting ASE page for folder " + folder
+ " with folder id " + folderId );
sendViewFolderGet( folderId.intValue() );
}
else
logger.debug( "ASE folder " + folder + " does not exist" );
result = true;
}
else
{
logger.debug( "Invalid ASE folder " + folder + " does not begin with \"ASE\\\"" );
}
}
}
return result;
}
private void sendViewFolderGet(int folderId) throws ParserConfigurationException, SAXException,
IOException, ASEClientException
{
HttpGet request = new HttpGet( this.location + "/FolderExplorer.aspx?fid=" + folderId );
HttpResponse response = null;
// Make the request
response = send( request, httpContext );
// response = httpClient.execute( request, httpContext );
logger.debug( response.getStatusLine().toString() );
// Read the full response and drop it.
HttpEntity responseEntity = response.getEntity();
EntityUtils.consume( responseEntity );
// Let the caller know if it succeeded
int responseCode = response.getStatusLine().getStatusCode();
if ( responseCode < 200 || responseCode >= 400 )
throw new ASEClientException(
"Failed to get folder page on the ASE instance with response code "
+ responseCode );
}
private HttpResponse getParentFolderPage(String url)
{
HttpResponse response = null;
try
{
HttpUriRequest request = new HttpGet( url );
response = send( request, httpContext );
// response = httpClient.execute( request, httpContext );
}
catch (IOException e)
{
e.printStackTrace();
}
return response;
}
/**
* Not neccessary for ASE 8.6, but it is nice to have just in case.
*
* @param response
* @return
*/
private String findViewState(HttpResponse response)
{
String viewstate = null;
if ( response != null )
{
HttpEntity responseEntity = response.getEntity();
try
{
BufferedReader reader = new BufferedReader( new InputStreamReader(
responseEntity.getContent() ) );
String line;
while ( ( line = reader.readLine() ) != null )
{
int idPos = line.lastIndexOf( "id=\"__VIEWSTATE\"" );
if ( idPos > 0 )
{
// Pattern pattern =
// Pattern.compile("<input.* id=\"__VIEWSTATE\".* value=\"(.*)\"");
Pattern pattern = Pattern.compile( "id=\"__VIEWSTATE\".* value=\"(.*)\"" );
Matcher matcher = pattern.matcher( line );
if ( matcher.find() )
{
viewstate = matcher.group( 1 );
logger.debug( "Found viewstate: " + viewstate );
}
}
}
EntityUtils.consume( responseEntity );
}
catch (IOException e)
{
e.printStackTrace();
}
}
return viewstate;
}
private void checkForError(Document doc) throws ASEClientException
{
Element rootElement = doc.getDocumentElement();
if ( "error".equalsIgnoreCase( rootElement.getTagName() ) )
{
logger.debug( "*** Error Occurred ***" );
NodeList nodes = rootElement.getChildNodes();
for ( int i = 0; i < nodes.getLength(); i++ )
{
Node node = nodes.item( i );
String nodeName = node.getLocalName();
if ( "code".equalsIgnoreCase( nodeName ) || "message".equalsIgnoreCase( nodeName ) )
{
logger.debug( node.getChildNodes().item( 0 ).getNodeValue() );
}
else if ( "help".equalsIgnoreCase( nodeName ) )
{
logger.debug( node.getAttributes().item( 0 ).getNodeValue() );
}
}
throw new ASEClientException( "Unexpected error." );
}
}
/**
* Setup namespace context to be used by XPath expressions
*/
private static NamespaceContext _nsContext;
static
{
_nsContext = new NamespaceContext()
{
public String getNamespaceURI(String prefix)
{
if ( prefix.equalsIgnoreCase( "ase" ) )
return "http://www.ibm.com/Rational/AppScanEnterprise";
return XMLConstants.NULL_NS_URI;
}
public String getPrefix(String arg0)
{
return null;
}
public Iterator<?> getPrefixes(String arg0)
{
return null;
}
};
}
private static void print(Object message)
{
System.out.println( message );
}
public void printDocument(Document doc) throws IOException, TransformerException
{
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" );
transformer.setOutputProperty( OutputKeys.METHOD, "xml" );
transformer.setOutputProperty( OutputKeys.INDENT, "yes" );
transformer.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
transformer.setOutputProperty( "{http://xml.apache.org/xslt}indent-amount", "4" );
StreamResult result = new StreamResult( new StringWriter() );
transformer.transform( new DOMSource( doc ), result );
logger.info( result.getWriter().toString() );
}
private static Map<String, String> parseOptions(String[] args)
{
Map<String, String> argsMap = new Hashtable<String, String>();
for ( int i = 0; i < args.length; i++ )
{
if ( args[i].startsWith( "-" ) && args.length > i + 1 )
{
argsMap.put( args[i].toLowerCase(), args[i + 1] );
}
}
return argsMap;
}
public static void main(String[] args)
{
Map<String, String> argsMap = parseOptions( args );
ASERestServicesClient app = null;
if ( args.length == 0 )
{
print( "java -jar ASEClient.jar -command getid -url <url> -folder <folder> -domain <domain> -user <username> -password <password> [-acceptssl true|false]" );
print( "java -jar ASEClient.jar -command create -url <url> -folder <folder> [-description <description>] [-contact <contact>] -domain <domain> -user <username> -password <password> [-acceptssl true|false]" );
print( "" );
print( "For test mode, the given URL maps to a test subdirectory of the current directory and all XML" );
print( "documents are named node.xml:" );
print( "java -jar ASEClient.jar -test true -command getid -url <url> -folder <folder>" );
print( "java -jar ASEClient.jar -test true -command create -url <url> -folder <folder> [-description <description>] [-contact <contact>]" );
}
String folder = null;
String description = null;
String contact = null;
// FIXME: Move testing out into a separate test class.
// Handle connection related parameters
if ( Boolean.valueOf( argsMap.get( "-test" ) ) )
{
testMode = true;
String location = argsMap.get( "-url" );
// app = new ASERestServicesClient( location );
}
else
{
String location = argsMap.get( "-url" );
String domain = argsMap.get( "-domain" );
String user = argsMap.get( "-user" );
String password = argsMap.get( "-password" );
boolean acceptSsl = Boolean.valueOf( argsMap.get( "-acceptssl" ) );
app = new ASERestServicesClient( location, domain, user, password, acceptSsl );
}
// Execute commands
if ( argsMap.get( "-command" ).equals( "getid" ) )
{
folder = argsMap.get( "-folder" );
Integer id = app.getFolderIdService( folder );
print( id );
}
/*
* else if (argsMap.get("-command").equals("create")) { folder =
* argsMap.get("-folder"); description = argsMap.get("-description");
* contact = argsMap.get("-contact"); if
* (app.createFolderService(folder, description, contact))
* print("Folder created"); else print("Folder not created"); }
*/
else
System.err.println( "Illegal argument: " + args[0] );
}
}