During a deployment of Ping Identity’s PingFederate product, we had a need to automate logging into the HTML Form Adapter. Whilst there are many ways to automate this, I really liked this one, and thought it worth sharing.
The solution has 2 components:
PhantomJS
PhantomJS is a headless browser this uses the WebKit browser by default, but can also use the Gecko engine courtesy of SlimerJS.
CasperJS
CasperJS started life as a bunch of scripts which drove PhantomJS & has since evolved into a full framework. This code utilizes version 1.1 of CasperJS, which despite being a development version is quite stable and is in fact the version the maintainers of CasperJS recommend.
Installation
Windows
Download phantomjs: http://phantomjs.org/download.html Unzip it to C:\phantomjs
Download the latest zip from this page: http://docs.casperjs.org/en/latest/installation.html and unzip to a safe & sensible location like C:\casperjs
Modify the PATH variable to make the binary accessible
setx PATH "%PATH%;C:\phantomjs\;C:\casperjs\bin"
Linux
$ wget https://phantomjs.googlecode.com/files/phantomjs-1.9.8-linux-x86_64.tar.bz2
$ tar xvjf phantomjs-1.9.8-linux-x86_64.tar.bz2
$ cd phantomjs-1.9.8-linux-x86_64
$ sudo ln -sf "$(pwd)"/bin/phantomjs /usr/local/bin/phantomjs
$ cd ..
$ git clone git://github.com/n1k0/casperjs.git
$ cd casperjs
$ sudo ln -sf "$(pwd)"/bin/casperjs /usr/local/bin/casperjs
Mac
brew update && brew install phantomjs
brew install casperjs --devel
Configuration
The configuration required is minimal.
PhantomJS by default utilizes SSLv3, so if you encounter the following warning and the script continually fails to complete:
[warning] [phantom] Loading resource failed with status=fail: https://fed.companyb.com/idp/prp.wsf?wa=wsignin1.0&wtrealm=https://applicationa.companyb.com/&wfresh=15&wctx=rm=0&id=passive&ru=%2fCIS%2f&wct=2014-11-19T05:34:06Z
The solution is to enable TLS using one of the following methods:
1) Specify the protocol to use each time you execute casperjs:
casperjs --ssl-protocol=tlsv1
2) Or, edit the file C:\casperjs\bin\casperjs and add the above switch to the ENGINE_ARGS
ENGINE_ARGS = ['ssl-protocol=tlsv1']
Solution code
"use strict";
var url = 'https://applicationa.companyb.com/',
username = 'mattheww',
password = 'password',
debug = true,
logLevel = null;
if (debug) {
logLevel = 'debug';
} else {
logLevel = 'info';
}
var casper = require('casper').create({
pageSettings: {
loadImages: false, // The WebPage instance used by Casper will
loadPlugins: false, // use these settings
userAgent: 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36'
},
logLevel: logLevel, // debug; info; warning; error
verbose: true, // log messages will be printed out to the console
options: {
waitTimeout: 25000, // Timeout of 25 sec
}
});
casper.start(url, function () {
this.log('Attempting login to ' + url, 'info');
this.fill('form', {
'pf.username': username,
'pf.pass': password
}, true);
});
casper.then(function() {
var currentURL = this.getCurrentUrl();
if (currentURL === url) {
console.log("SUCCESS!: We're in " + currentURL);
} else if (/prp.ping$/i.test(currentURL)) {
console.log("FAIL!: We're stuck on prp.ping");
if (debug) {
// Dump whole page for visual inspection
var page = this.evaluate(function() {
return document;
});
this.echo(page.all[0].outerHTML);
} else {
if (this.exists('div.ping-body form div div')) {
var msg = this.fetchText('div.ping-body form div div');
var msgArray = msg.split('\t');
this.echo(msgArray[0]);
} else if (this.exists('div.ping-body')) {
var pingBody = this.fetchText('div.ping-body');
if (pingBody.indexOf('Access denied (403)') >= 0 ) {
this.echo(pingBody);
}
}
}
}
});
casper.run();
When successful,the output should resemble below
C:\casperjs\bin>casperjs.exe ..\mw\run.js
[info] [phantom] Starting...
[info] [phantom] Running suite: 3 steps
[info] [phantom] Step anonymous 2/3 https://fed.companyb.com/idp/ZVlk9/resume/idp/prp.ping (HTTP 200)
[info] [phantom] Attempting login to https://applicationa.companyb.com/
[info] [remote] attempting to fetch form element from selector: 'form'
[info] [remote] submitting form to /idp/ZVlk9/resume/idp/prp.ping, HTTP POST
[info] [phantom] Step anonymous 2/3: done in 154ms.
[info] [phantom] Step anonymous 3/3 https://applicationa.companyb.com/ (HTTP 200)
SUCCESS!: We're in https://applicationa.companyb.com/
[info] [phantom] Step anonymous 3/3: done in 730ms.
[info] [phantom] Done 3 steps in 749ms
A failure will resemble the following:
C:\casperjs\bin>casperjs ..\mw\run.js
[info] [phantom] Starting...
[info] [phantom] Running suite: 3 steps
[info] [phantom] Step anonymous 2/3 https://fed.companyb.com/idp/82l2u/resume/idp/prp.ping (HTTP 200)
[info] [phantom] Attempting login to https://applicationa.companyb.com/
[info] [remote] attempting to fetch form element from selector: 'form'
[info] [remote] submitting form to /idp/82l2u/resume/idp/prp.ping, HTTP POST
[info] [phantom] Step anonymous 2/3: done in 163ms.
[info] [phantom] Step anonymous 3/3 https://fed.companyb.com/idp/82l2u/resume/idp/prp.ping (HTTP 200)
FAIL!: We're stuck on prp.ping
Authentication failed. You have used an invalid username and/or password.
[info] [phantom] Step anonymous 3/3: done in 469ms.
[info] [phantom] Done 3 steps in 485ms
C:\casperjs\bin>