SAML and the Command Line
Unfortunately the API only offers authentication via SAML, OAuth or BasicAuth are missing. So any application interacting with the API needs to do The SAML Dance. That's annoying when you have an UI to use, and a formidable challenge when you have a command line application, like a cron Job running unsupervised at interval.
One lovely step in the process: the IBM IdP returns a HTML page with a hidden form containing the SAML assertion result to be posted back to the application provider. Quite interesting, when your application provider is a command line app. Let's get to work.
The script is written in node.js and uses request and fast-html-parser npm package. The first step is to load the login form (which comes with a first set of cookies)
var requestOptionsTemplate = {
headers: {
'Origin': 'https://api.notes.ap.collabserv.com/api/traveler/',
'User-Agent': 'ancy CommandLine Script',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': 1
},
'method': 'GET'
};
function scLoginPart1() {
console.log('Authenticating to SmartCloud ...');
var requestOptions = Object.assign({}, requestOptionsTemplate);
requestOptions.url = 'https://apps.na.collabserv.com/manage/account/dashboardHandler/input';
request(requestOptions, scLoginPart2);
}
The function calls the URL where the login form can be found. The result gets delivered to the function
scLoginPart2
. That function makes use of a global configuration variable config
that was created through const config = require("./config.json")
and contains all the credentials we need. Step2 submits the form and hands over to Step3.
function scLoginPart2(err, httpResponse, body) {
if (err) {
return console.error(err);
}
// Capture cookies
var outgoingCookies = captureCookies(httpResponse);
var requestOptions = Object.assign({}, requestOptionsTemplate);
requestOptions.headers.Cookie = outgoingCookies.join('; ');
requestOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
requestOptions.method = 'POST';
requestOptions.url = 'https://apps.ap.collabserv.com/pkmslogin.form';
requestOptions.form = {
'login-form-type': 'pwd',
'error-code': '',
'username': config.smartcloud.user,
'password': config.smartcloud.password,
'show_login': 'showLoginAgain'
}
request(requestOptions, scLoginPart3);
}
function captureCookies(response) {
var incomingCookies = response.headers['set-cookie'];
var outgoingCookies = [];
if (incomingCookies) {
incomingCookies.forEach(function(cookie) {
outgoingCookies.push[cookie.split(';'](0));
});
}
// Array, allows for duplicate coolie names
return outgoingCookies;
}
Part 3 / 4 finally collect all the cookies we need, so to turn attention to getting the API token in step 5
function scLoginPart3(err, httpResponse, body) {
if (err) {
console.error('Login failed miserably');
return console.error(err);
}
// Login returns not 200 but 302
// see https://developer.ibm.com/social/2015/06/23/slight-changes-to-the-form-based-login/
if (httpResponse.statusCode !== 302) {
return console.error('Wrong status code received: ' + httpResponse.statusCode);
}
var outgoingCookies = captureCookies(httpResponse);
var redirect = httpResponse.headers.location;
// This is the 3rd request we need to make to get finally all cookies for app.na
var requestOptions = Object.assign({}, requestOptionsTemplate);
requestOptions.headers.Cookie = outgoingCookies.join('; ');
requestOptions.url = redirect;
request(requestOptions, scLoginPart4);
}
function scLoginPart4(err, httpResponse, body) {
if (err) {
console.error('Login redirect failed miserably');
return console.error(err);
}
var cookieHarvest = captureCookies(httpResponse);
// Now we have some cookies in app, we need the SAML dance for api.notes
scLoginPart5(cookieHarvest)
}
In Part 5 we first request the URL with actual data (devices in our case), but get another SAML dance step, since we have
apps.na
vs api.notes
in the URLRead more
Posted by Stephan H Wissel on 30 January 2017 | Comments (1) | categories: JavaScript NodeJS