panel.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. function Console() {}
  2. Console.Type = {
  3. LOG: "log",
  4. DEBUG: "debug",
  5. INFO: "info",
  6. WARN: "warn",
  7. ERROR: "error",
  8. GROUP: "group",
  9. GROUP_COLLAPSED: "groupCollapsed",
  10. GROUP_END: "groupEnd"
  11. };
  12. Console.addMessage = function(type, format, args) {
  13. chrome.runtime.sendMessage({
  14. command: "sendToConsole",
  15. tabId: chrome.devtools.inspectedWindow.tabId,
  16. args: escape(JSON.stringify(Array.prototype.slice.call(arguments, 0)))
  17. });
  18. };
  19. // Generate Console output methods, i.e. Console.log(), Console.debug() etc.
  20. (function() {
  21. var console_types = Object.getOwnPropertyNames(Console.Type);
  22. for (var type = 0; type < console_types.length; ++type) {
  23. var method_name = Console.Type[console_types[type]];
  24. Console[method_name] = Console.addMessage.bind(Console, method_name);
  25. }
  26. })();
  27. SAMLChrome.controller('PanelController', function PanelController($scope, $http, toolbar) {
  28. $scope.uniqueid = 1000000;
  29. $scope.activeId = null;
  30. $scope.requests = {};
  31. $scope.showSamlRequests = {};
  32. $scope.showAll = false;
  33. $scope.limitNetworkRequests = true;
  34. $scope.showOriginalSAML = false;
  35. $scope.currentDetailTab = "tab-saml";
  36. $scope.myCodeMirror = null;
  37. $scope.activeCookies = [];
  38. $scope.activeHeaders = [];
  39. $scope.activePostData = [];
  40. $scope.activeRequest = [];
  41. $scope.activeResponseData = [];
  42. $scope.activeResponseCookies = [];
  43. $scope.activeResponseHeaders = [];
  44. $scope.activeSaml = null;
  45. $scope.showIncomingRequests = true;
  46. $scope.init = function(type) {
  47. $('#tabs').tabs();
  48. $scope.initChrome();
  49. this.createToolbar();
  50. };
  51. $scope.initChrome = function() {
  52. key('⌘+k, ctrl+l', function() {
  53. $scope.$apply(function() {
  54. $scope.clear();
  55. });
  56. });
  57. chrome.devtools.network.onRequestFinished.addListener(function(request) {
  58. // do not show requests to chrome extension resources
  59. if (request.request.url.startsWith("chrome-extension://")) {
  60. return;
  61. }
  62. $scope.handleSAMLHeaders(request);
  63. });
  64. };
  65. $scope.handleSAMLHeaders = function(har_entry) {
  66. let decoded_saml_message;
  67. const request_method = har_entry.request.method;
  68. const request_url = har_entry.request.url;
  69. const response_status = har_entry.response.status;
  70. const saml_request_string = "SAMLRequest=";
  71. const saml_response_string = "SAMLResponse=";
  72. let found_saml = false;
  73. var index_of_saml_request_string = request_url.indexOf(saml_request_string);
  74. if (index_of_saml_request_string > -1) {
  75. Console.log("SAML Request Method: " + request_method);
  76. Console.log("SAML Request URL: " + request_url);
  77. var index_of_next_param = request_url.indexOf("&", index_of_saml_request_string);
  78. if (index_of_next_param < 0) {
  79. index_of_next_param = request_url.length;
  80. }
  81. //assumes that the GET request is http(s)://host/sso/idp?SAMLRequest=xxxxx&RelayState=yyyy
  82. const saml_message = request_url.substr(index_of_saml_request_string + saml_request_string.length, index_of_next_param - (index_of_saml_request_string + saml_request_string.length));
  83. //requires inflating
  84. decoded_saml_message = RawDeflate.inflate(window.atob(unescape(saml_message)));
  85. $scope.addRequest(har_entry, request_method, request_url, response_status, decoded_saml_message);
  86. Console.log("SAML Request Data: " + decoded_saml_message);
  87. found_saml = true;
  88. }
  89. let har_post_data = null;
  90. if (har_entry.request != null && har_entry.request.postData != null) {
  91. har_post_data = har_entry.request.postData.text;
  92. };
  93. if (har_post_data != null) {
  94. if (har_post_data.indexOf(saml_request_string) > -1) {
  95. decoded_saml_message = $scope.getDecodedSamlMessageFromPostData("Request", request_method, request_url, har_post_data, saml_request_string, response_status, har_entry);
  96. Console.log("SAML Request Data: " + decoded_saml_message);
  97. found_saml = true;
  98. } else if (har_post_data.indexOf(saml_response_string) > -1) {
  99. decoded_saml_message = $scope.getDecodedSamlMessageFromPostData("Response", request_method, request_url, har_post_data, saml_response_string, response_status, har_entry);
  100. Console.log("SAML Response Data: " + decoded_saml_message);
  101. found_saml = true;
  102. }
  103. }
  104. if (found_saml === false) {
  105. $scope.addRequest(har_entry, request_method, request_url, response_status, null);
  106. }
  107. };
  108. $scope.getDecodedSamlMessageFromPostData = function(request_response_string, request_method, request_url, har_post_data, saml_string, response_status, har_entry) {
  109. const index_of_saml_string = har_post_data.indexOf(saml_string);
  110. let saml_message = har_post_data.substr(index_of_saml_string + saml_string.length, har_post_data.length - (index_of_saml_string + saml_string.length));
  111. const index_of_next_param = saml_message.indexOf("&", 0);
  112. if (index_of_next_param > -1) {
  113. saml_message = saml_message.substr(0, index_of_next_param);
  114. }
  115. //using the window.atob base64 decoding method as it seems to work pretty well
  116. const decoded_saml_message = window.atob(unescape(saml_message));
  117. $scope.addRequest(har_entry, request_method, request_url, response_status, decoded_saml_message);
  118. return decoded_saml_message;
  119. };
  120. $scope.createToolbar = function() {
  121. toolbar.createButton('search', 'Search SAML', false, function() {
  122. $scope.$apply(function() {
  123. if ($scope.myCodeMirror) {
  124. $scope.myCodeMirror.execCommand("find");
  125. }
  126. });
  127. });
  128. toolbar.createToggleButton('file-text-o', 'SAML Format', false, function() {
  129. $scope.$apply(function() {
  130. $scope.showOriginalSAML = !$scope.showOriginalSAML;
  131. $scope.displaySaml();
  132. });
  133. }, false);
  134. toolbar.createButton('chain', 'Update All Link/Form Targets to _self', false, function() {
  135. $scope.$apply(function() {
  136. chrome.runtime.sendMessage({
  137. command: "scrub",
  138. tabId: chrome.devtools.inspectedWindow.tabId
  139. });
  140. });
  141. });
  142. toolbar.createButton('download', 'Export', false, function() {
  143. $scope.$apply(function() {
  144. var blob = new Blob([JSON.stringify($scope.requests)], {type: "application/json;charset=utf-8"});
  145. saveAs(blob, "SAMLChromeExport.json");
  146. });
  147. });
  148. toolbar.createButton('upload', 'Import', true, function() {
  149. $scope.$apply(function() {
  150. $('#ImportInput').click();
  151. });
  152. });
  153. toolbar.createToggleButton('tasks', 'SAML Filter', false, function() {
  154. $scope.$apply(function() {
  155. $scope.showAll = !$scope.showAll;
  156. $scope.showTraffic();
  157. });
  158. }, false);
  159. toolbar.createToggleButton('list', 'Limit network requests to 500', false, function() {
  160. $scope.$apply(function() {
  161. $scope.limitNetworkRequests = !$scope.limitNetworkRequests;
  162. });
  163. }, true);
  164. toolbar.createButton('ban', 'Clear', false, function() {
  165. $scope.$apply(function() {
  166. $scope.clear();
  167. });
  168. });
  169. $('.toolbar').replaceWith(toolbar.render());
  170. //clears the input value so you can reload the same file
  171. document.getElementById('ImportInput').addEventListener('click', function() {this.value=null;}, false);
  172. document.getElementById('ImportInput').addEventListener('change', readFile, false);
  173. function readFile (evt) {
  174. const files = evt.target.files;
  175. const file = files[0];
  176. const reader = new FileReader();
  177. reader.onload = function() {
  178. $scope.importFile(this.result);
  179. }
  180. reader.readAsText(file)
  181. }
  182. };
  183. $scope.importFile = function(data) {
  184. $scope.$apply(function() {
  185. const importHar = JSON.parse(data);
  186. for (i in importHar)
  187. {
  188. $scope.handleSAMLHeaders(importHar[i]);
  189. }
  190. });
  191. }
  192. $scope.addRequest = function(data, request_method, request_url, response_status, decoded_saml_message) {
  193. $scope.$apply(function() {
  194. const requestId = $scope.uniqueid;
  195. $scope.uniqueid = $scope.uniqueid + 1;
  196. if (data.request != null) {
  197. data["request_data"] = $scope.createKeypairs(data.request);
  198. if (data.request.cookies != null) {
  199. data.cookies = $scope.createKeypairsDeep(data.request.cookies);
  200. }
  201. if (data.request.headers != null) {
  202. data.headers = $scope.createKeypairsDeep(data.request.headers);
  203. }
  204. if (data.request.postData != null) {
  205. data.postData = $scope.createKeypairsDeep(data.request.postData.params);
  206. }
  207. }
  208. if (data.response != null) {
  209. data["response_data"] = $scope.createKeypairs(data.response);
  210. if (data.response.cookies != null) {
  211. data["response_cookies"] = $scope.createKeypairsDeep(data.response.cookies);
  212. }
  213. if (data.response.headers != null) {
  214. data["response_headers"] = $scope.createKeypairsDeep(data.response.headers);
  215. }
  216. }
  217. data["request_method"] = request_method;
  218. data["request_url"] = request_url;
  219. data["response_status"] = response_status;
  220. data["id"] = requestId;
  221. data["saml"] = decoded_saml_message;
  222. $scope.requests[requestId] = data;
  223. if (decoded_saml_message != null || $scope.showAll === true) {
  224. $scope.showSamlRequests[requestId] = data;
  225. }
  226. $scope.cleanRequests();
  227. if ($scope.showIncomingRequests && $scope.showSamlRequests[requestId]) {
  228. $scope.setActive(requestId);
  229. }
  230. });
  231. };
  232. $scope.cleanRequests = function() {
  233. if ($scope.limitNetworkRequests === true) {
  234. const keys = Object.keys($scope.requests).reverse().slice(500);
  235. keys.forEach(function(key) {
  236. if ($scope.requests[key]) {
  237. delete $scope.requests[key];
  238. }
  239. if ($scope.showSamlRequests[key]) {
  240. delete $scope.showSamlRequests[key];
  241. }
  242. });
  243. }
  244. }
  245. $scope.clear = function() {
  246. $scope.requests = {};
  247. $scope.activeId = null;
  248. $scope.showSamlRequests = {};
  249. $scope.activeCookies = [];
  250. $scope.activeHeaders = [];
  251. $scope.activePostData = [];
  252. $scope.activeRequest = [];
  253. $scope.activeResponseData = [];
  254. $scope.activeResponseCookies = [];
  255. $scope.activeResponseHeaders = [];
  256. $scope.activeSaml = null;
  257. $scope.showIncomingRequests = true;
  258. document.getElementById("tab-saml-codemirror").style.visibility = "hidden";
  259. };
  260. $scope.setActive = function(requestId) {
  261. if (!$scope.requests[requestId]) {
  262. return;
  263. }
  264. $scope.activeId = requestId;
  265. $scope.activeCookies = $scope.requests[requestId].cookies;
  266. $scope.activeHeaders = $scope.requests[requestId].headers;
  267. $scope.activePostData = $scope.requests[requestId].postData;
  268. $scope.activeRequest = $scope.requests[requestId].request_data;
  269. $scope.activeResponseData = $scope.requests[requestId].response_data;
  270. $scope.activeResponseCookies = $scope.requests[requestId].response_cookies;
  271. $scope.activeResponseHeaders = $scope.requests[requestId].response_headers;
  272. $scope.activeSaml = $scope.requests[requestId].saml;
  273. const lastRequestId = Object.keys($scope.showSamlRequests)[Object.keys($scope.showSamlRequests).length - 1];
  274. $scope.showIncomingRequests = requestId == lastRequestId;
  275. if ($scope.activeSaml == null && $scope.currentDetailTab === "tab-saml") {
  276. $("#tabs").tabs("option", "active", $("tab-request" + "Selector").index() - 1);
  277. }
  278. };
  279. $scope.getClass = function(requestId) {
  280. if (requestId === $scope.activeId) {
  281. return 'selected';
  282. } else {
  283. return '';
  284. }
  285. };
  286. $scope.showTraffic = function() {
  287. $scope.showSamlRequests = {};
  288. $.each($scope.requests, function(request) {
  289. if ($scope.requests[request].saml != null || $scope.showAll === true) {
  290. $scope.showSamlRequests[request] = $scope.requests[request];
  291. }
  292. });
  293. var lastRequestId = Object.keys($scope.showSamlRequests)[Object.keys($scope.showSamlRequests).length - 1];
  294. $scope.showIncomingRequests = $scope.activeId == lastRequestId;
  295. }
  296. $scope.createKeypairs = function(data) {
  297. let keypairs = [];
  298. if (!(data instanceof Object)) {
  299. return keypairs;
  300. }
  301. $.each(data, function(key, value) {
  302. if (!(value instanceof Object)) {
  303. keypairs.push({
  304. name: key,
  305. value: value
  306. });
  307. }
  308. });
  309. return keypairs;
  310. };
  311. $scope.createKeypairsDeep = function(data) {
  312. let keypairs = [];
  313. if (!(data instanceof Object)) {
  314. return keypairs;
  315. }
  316. $.each(data, function(key, value) {
  317. keypairs.push({
  318. name: value.name,
  319. value: value.value
  320. });
  321. });
  322. return keypairs;
  323. };
  324. $scope.$watch('activeSaml', function() {
  325. $scope.displaySaml();
  326. });
  327. $scope.isSaml = function(requestId) {
  328. if ($scope.requests[requestId].saml != null) {
  329. return "saml";
  330. }
  331. }
  332. $scope.getTrafficStyle = function(request) {
  333. if (request.saml != null) {
  334. const status = request.response_status;
  335. if (status >= 200 && status < 300) {
  336. return "success";
  337. } else if (status >= 300 && status < 400) {
  338. return "redirect";
  339. } else {
  340. return "error"
  341. }
  342. }
  343. return "";
  344. }
  345. $scope.selectDetailTab = function(tabId) {
  346. $scope.currentDetailTab = tabId;
  347. if (tabId === "tab-saml") {
  348. $scope.displaySaml();
  349. }
  350. }
  351. $scope.displaySaml = function() {
  352. if ($scope.activeSaml != null) {
  353. document.getElementById("tab-saml-codemirror").style.visibility = "visible";
  354. let samlContent = $scope.activeSaml;
  355. if (!$scope.showOriginalSAML) {
  356. samlContent = $scope.getPrettyXML(samlContent);
  357. }
  358. if ($scope.myCodeMirror) {
  359. $scope.myCodeMirror.getDoc().setValue(samlContent);
  360. return;
  361. }
  362. document.getElementById("tab-saml-codemirror").innerHTML = "";
  363. const myCodeMirror = CodeMirror(document.getElementById("tab-saml-codemirror"), {
  364. value: samlContent,
  365. mode: "xml",
  366. lineNumbers: true,
  367. lineWrapping: true
  368. });
  369. $scope.myCodeMirror = myCodeMirror;
  370. }
  371. }
  372. $scope.getPrettyXML = function(source) {
  373. return new XmlBeautify().beautify(source,
  374. {
  375. indent: " ", //indent pattern like white spaces
  376. useSelfClosingElement: false //true:use self-closing element when empty element.
  377. });
  378. }
  379. });