From a75e5e12a965a4fe6658ecf95af7b0cbecc42092 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 11 Sep 2018 10:36:58 +0200 Subject: [PATCH] init --- .gitignore | 2 + index.js | 290 ++++++++++++++++++++++++++++++++++++++++++ package.json | 17 +++ src/console/input.js | 59 +++++++++ src/console/output.js | 37 ++++++ src/cookie-bag.js | 53 ++++++++ src/logger.js | 26 ++++ src/preg-match.js | 5 + 8 files changed, 489 insertions(+) create mode 100644 .gitignore create mode 100644 index.js create mode 100644 package.json create mode 100644 src/console/input.js create mode 100644 src/console/output.js create mode 100644 src/cookie-bag.js create mode 100644 src/logger.js create mode 100644 src/preg-match.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a924327 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +login +node_modules/ diff --git a/index.js b/index.js new file mode 100644 index 0000000..dd20aec --- /dev/null +++ b/index.js @@ -0,0 +1,290 @@ +"use strict"; + +var Crawler = require("crawler"); +var path = require('path'); +var output = new(require('./src/console/output')); +var input = new(require('./src/console/input'))(process); +var pregMatch = require('./src/preg-match'); +var logger = require('./src/logger.js'); +var cookieBag = new(require('./src/cookie-bag.js')); + +process.chdir(path.dirname(input.script)); + +var isDebugMode = input.has('debug'); + +if (!isDebugMode) { + process.on('uncaughtException', function() { + output.json({ + error: true, + message: 'UncaughtException' + }); + + process.exit(1); + }); +} + +if (input.has('help')) { + output.write([ + process.argv[0], + process.argv[1], + '[--crawler-debug]', + '[--crawler-info]', + '[--crawler-error]', + ].join(' ')); + + process.exit(1); +} + +var handlerCrawlerError = function(response, e) { + output.json({ + error: true, + message: { + body: response.body, + response: response, + error: e, + }, + }, true); +} + +var handlerCrawlerException = function(e, done) { + if (isDebugMode) { + throw e; + } else { + done(); + + return output.json({ + error: true, + message: e + }); + } +} + +var headers = function(crawler) { + var datas = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 5.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'fr,fr-FR;q=0.5', + 'Cookie': cookieBag.toString(), + }; + + if (crawler.storage.hasOwnProperty('referer')) { + datas['referer'] = crawler.storage.referer; + } + + return datas; +} + +var createCrawlerOptions = function(config) { + var options = { + maxConnections: 10, + retries: 1, + logger: logger, + jQuery: false, + }; + + config = config || {}; + + for (var i in config) { + options[i] = config[i]; + } + + return options; +} + +var createCrawler = function(options) { + options = createCrawlerOptions(options || {}); + + if (!options.hasOwnProperty('callback') && options.hasOwnProperty('onSuccess')) { + options.callback = function(error, response, done) { + if (error) { + handlerCrawlerError(response, error); + } else { + try { + options.onSuccess(response, done); + } catch (e) { + return handlerCrawlerException(e, done); + } + } + + done(); + } + } + + var c = new Crawler(options); + + c.storage = {}; + + c.runCrawl = function(url) { + c.options.headers = headers(c); + + return c.queue(url); + }; + + return c; +} + +try { + var UrlRefreshRequester = createCrawler({ + onSuccess: function(response) { + cookieBag.handleResponse(response); + + var urlRefresh = pregMatch(/"urlRefresh":"([^"]+)"/, response.body)[1]; + + LoginFormRequester.runCrawl(urlRefresh); + }, + }); + + var LoginFormRequester = createCrawler({ + onSuccess: function(response) { + cookieBag.handleResponse(response); + + var urlPost = pregMatch(/"urlPost":"([^"]+)"/, response.body)[1]; + var urlLogin = pregMatch(/"urlLogin":"([^"]+)"/, response.body)[1]; + + var data = { + canary: pregMatch(/"canary":"([^"]+)"/, response.body)[1], + ctx: pregMatch(/ctx=(.+)/, urlLogin)[1], + hpgrequestid: pregMatch(/"sessionId":"([^"]+)"/, response.body)[1], + flowToken: pregMatch(/"sFT":"([^"]+)"/, response.body)[1], + f1: 'Suivant', + NewUser: 1, + CookieDisclosure: 1, + fspost: 0, + IsFidoSupported: 0, + FoundMSAs: null, + i2: 1, + i13: 0, + i17: '', + i18: '', + i19: 19471, + ps: 2, + psRNGCDefaultType: '', + psRNGCEntropy: '', + psRNGCSLK: '', + PPSX: '', + FoundMSAs: '', + type: 11, + LoginOptions: 3, + Irt: '', + IrtPartition: '', + hisRegion: '', + hisScaleUnit: '', + loginfmt: process.env.MS_OFFICE365_LOGIN, + login: process.env.MS_OFFICE365_LOGIN, + passwd: process.env.MS_OFFICE365_PASSWORD, + } + + LoginRequester.options.form = data; + + LoginRequester.runCrawl('https://login.microsoftonline.com' + urlPost) + } + }); + + var LoginRequester = createCrawler({ + method: 'POST', + onSuccess: function(response) { + cookieBag.handleResponse(response); + + var data = { + canary: pregMatch(/"canary":"([^"]+)"/, response.body)[1], + ctx: pregMatch(/"sCtx":"([^"]+)"/, response.body)[1], + hpgrequestid: pregMatch(/"sessionId":"([^"]+)"/, response.body)[1], + flowToken: pregMatch(/"sFT":"([^"]+)"/, response.body)[1], + LoginOptions: 1, + } + + KmsiRequester.options.form = data; + + KmsiRequester.runCrawl('https://login.microsoftonline.com/kmsi'); + } + }); + + var KmsiRequester = createCrawler({ + method: 'POST', + jQuery: true, + onSuccess: function(response) { + cookieBag.handleResponse(response); + + var data = { + code: response.$('[name="code"]').val(), + id_token: response.$('[name="id_token"]').val(), + session_state: response.$('[name="session_state"]').val(), + correlation_id: response.$('[name="correlation_id"]').val(), + }; + + SiteRequester.options.form = data; + SiteRequester.storage.referer = 'https://login.microsoftonline.com/kmsi'; + SiteRequester.runCrawl(process.env.MS_OFFICE365_SITE + '_forms/default.aspx'); + } + }); + + var SiteRequester = createCrawler({ + method: 'POST', + jQuery: true, + onSuccess: function(response, done) { + cookieBag.handleResponse(response); + + if (SiteRequester.storage.hasOwnProperty('finish')) { + output.json({ + rtFa: cookieBag.get('rtFa'), + FedAuth: cookieBag.get('FedAuth'), + }, true); + + return; + } + + if (SiteRequester.storage.hasOwnProperty('kmsi')) { + delete SiteRequester.storage.kmsi; + + SiteRequester.storage.finish = true; + + return KmsiRequester.options.onSuccess(response); + } + + if (response.headers.hasOwnProperty('location')) { + SiteRequester.options.method = 'GET'; + + var location = response.headers.location; + + SiteRequester.runCrawl(location); + } else { + var urlPost = response.$('form').attr('action'); + + var data = { + code: response.$('[name="code"]').val(), + id_token: response.$('[name="id_token"]').val(), + session_state: response.$('[name="session_state"]').val(), + correlation_id: response.$('[name="correlation_id"]').val(), + }; + + var params = urlPost.split('?')[1].split('&'); + + params.forEach(function(param) { + var name = pregMatch(/([a-zA-Z0-9_-]+)=/, param)[1]; + var value = pregMatch(/=(.+)/, param)[1]; + + data[name] = decodeURIComponent(value); + }); + + SiteRequester.options.method = 'POST'; + SiteRequester.options.form = data; + SiteRequester.storage.referer = 'https://login.microsoftonline.com/kmsi'; + SiteRequester.storage.kmsi = true; + SiteRequester.runCrawl(urlPost); + } + }, + }); + + UrlRefreshRequester.runCrawl(process.env.MS_OFFICE365_SITE); +} catch (e) { + if (isDebugMode) { + throw e; + } else { + output.json({ + error: true, + message: e + }); + } + + process.exit(1); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6ac4e19 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "sharepoint-webdav-oauth2-bot", + "version": "1.0.0", + "description": "sharepoint-webdav-oauth2-bot", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Simon Vieille ", + "license": "ISC", + "dependencies": { + "class.extend": "^0.9.2", + "crawler": "^1.1.2", + "minimist": "^1.2.0", + "trim": "0.0.1" + } +} diff --git a/src/console/input.js b/src/console/input.js new file mode 100644 index 0000000..58fbcbb --- /dev/null +++ b/src/console/input.js @@ -0,0 +1,59 @@ +"use strict"; + +var Minimist = require('minimist'); +var Class = require('class.extend'); + +var Input = Class.extend('Input', { + /** + * Constructor. + * + * @param object process + */ + init: function(process) { + this.args = Minimist(process.argv.slice(2)); + this.node = process.argv[0]; + this.script = process.argv[1]; + }, + + /** + * Return the value of the given name argument. + * + * @param string name + * @param mixed default + * + * @return mixed + */ + get: function(name, defaultValue) { + if (this.has(name)) { + return this.args[name]; + } + + if (defaultValue !== undefined) { + return defaultValue; + } + + return null; + }, + + /** + * Check the given argument name exists. + * + * @param string name + * + * @return boolean + */ + has: function(name) { + return this.args.hasOwnProperty(name); + }, + + /** + * Return if args is empty. + * + * @return boolean + */ + empty: function() { + return Object.keys(this.args).length === 1; + }, +}); + +module.exports = Input; diff --git a/src/console/output.js b/src/console/output.js new file mode 100644 index 0000000..b24e606 --- /dev/null +++ b/src/console/output.js @@ -0,0 +1,37 @@ +"use strict"; + +var Class = require('class.extend'); + +var Output = Class.extend('Output', { + /** + * Convert and print data to json. + * + * @param mixed data + */ + json: function(data, pretty) { + data = JSON.stringify( + data, + function(key, value) { + if (value === undefined) { + return null; + } + + return value; + }, + pretty ? 2 : null + ); + + return this.write(data); + }, + + /** + * Print data. + * + * @param mixed data + */ + write: function(data) { + console.log(data); + } +}); + +module.exports = Output; diff --git a/src/cookie-bag.js b/src/cookie-bag.js new file mode 100644 index 0000000..45cb2f3 --- /dev/null +++ b/src/cookie-bag.js @@ -0,0 +1,53 @@ +var trim = require('trim'); + +var CookieBag = function() { + this.bag = []; +} + +CookieBag.prototype.add = function(name, value) { + this.bag[name] = name + '=' + value; +} + +CookieBag.prototype.get = function(name) { + var str = this.bag[name] || null; + + if (!str) { + return; + } + + var index = str.indexOf('='); + + return str.substr(index + 1); +} + +CookieBag.prototype.toString = function() { + var data = []; + + for (var i in this.bag) { + data.push(this.bag[i]); + } + + return data.join('; '); +} + +CookieBag.prototype.handleResponse = function(response) { + var headers = response.headers || {}; + var cookies = headers['set-cookie'] || []; + var that = this; + + cookies.forEach(function(cookie) { + var def = cookie.split(';')[0]; + var index = def.indexOf('='); + + var name = def.substr(0, index); + var value = def.substr(index + 1); + + that.add(name, value); + }); +} + +CookieBag.prototype.clean = function() { + this.bag = []; +} + +module.exports = CookieBag; diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..e52317d --- /dev/null +++ b/src/logger.js @@ -0,0 +1,26 @@ +var output = new(require('./console/output')); +var input = new(require('./console/input'))(process); + +var logCrawlerDebug = input.has('crawler-debug'); +var logCrawlerInfo = input.has('crawler-info'); +var logCrawlerError = input.has('crawler-error'); + +var writeLog = function(level, message) { + return output.write('[' + level.toUpperCase() + '] ' + message); +} + +module.exports = { + log: function(level, message) { + if (level === 'debug' && logCrawlerDebug) { + return writeLog(level, message); + } + + if (level === 'info' && logCrawlerInfo) { + return writeLog(level, message); + } + + if (level === 'error' && logCrawlerError) { + return writeLog(level, message); + } + }, +}; diff --git a/src/preg-match.js b/src/preg-match.js new file mode 100644 index 0000000..9acecb5 --- /dev/null +++ b/src/preg-match.js @@ -0,0 +1,5 @@ +var pregMatch = function(regex, content) { + return content.match(regex); +} + +module.exports = pregMatch;