Tag: javascript

Web Redirection Based on Typed URL

I have no idea why I am so pleased with this simple HTML code, but I am! My current project is to move all of our Tableau servers to different servers running a newer version of Windows. When I first got involved with the project, it seemed rather difficult (there was talk of manually recreating all of the permissions on each item!!) … but, some review of the vendors documentation let me to believe one could build a same-version server elsewhere (newer Windows, out in magic cloudy land, but the same Tableau version), back up the data from the old server, restore it to the new one, and be done. It’s not quite that simple — I had to clear out the SAML config & manually reconfigure it so the right elements get added into the Java keystore, access to the local Postgresql database needed to be manually configured, a whole bunch of database drivers needed to be installed, and the Windows registry of ODBC connections needed to be exported/imported. But the whole process was a lot easier than what I was first presented.

Upgrading the first production server was mostly seamless — except users appear to have had the server’s actual name. Instead of accessing https://tableau.example.com, they were typing abcwxy129.example.com. And passing that link around as “the link” to their dashboard. And, upon stopping the Tableau services on the old server … those links started to fail. Now, I could have just CNAMED abcwxy129 over to tableau and left it at that. But letting users continue to do the wrong thing always seems to come back and haunt you (if nothing else, the OS folks own the namespace of servers & are allowed to re-use or delete those hostnames at will). So I wanted something that would take whatever https://tableau.example.com/#/site/DepartMent/workbooks/3851/Views kind of URL a user provided and give them the right address. And, since this was Windows, to do so with IIS without the hassle of integrating PHP or building a C# project. Basically, I wanted to do it within basic HTML. Which meant JavaScript.

And I did it — using such a basic IIS installation that the file is named something like iisstart.htm so I didn’t have to change the default page name. I also redirected 404 to / so any path under the old server’s name will return the redirection landing page.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
		<title>This Tableau server has moved</title>
		
		<style type="text/css">
			<!--
			body {
				color:#000000;
				background-color:#eeeeee;
				margin:0;
			}
			-->
		</style>
	</head>
	<body>
		<P><ul>
		<h2>The Tableau server has moved. </h2>
		<P>The location you accessed, <span style="white-space: nowrap" id="oldurl"></span>, is no longer available.<br><br> Please update your link to use <span style="white-space: nowrap"  id="newurl"></span></p>
		</ul></P>
	
		<script>
			let strOldURL = window.location.href;

			let strNewURL = strOldURL.replace(/hostname.example.com/i,"tableau.example.com");
			strNewURL = strNewURL.replace(/otherhostname.example.com/i,"tableau.example.com");

			document.getElementById("oldurl").innerHTML = window.location.href;
			document.getElementById("newurl").innerHTML = "<a href=" + strNewURL + ">" + strNewURL + "</a>";
		</script>
	
	</body>
</html>

Exporting Microsoft Stream Transcripts with Timecode URLs

I’d created a couple of quick code snippets to export Microsoft Stream transcripts & someone asked if you could include a way for users to click on a hyperlink and pop into the video at the right spot for the line in the transcript they clicked. Seemed like a good idea — I’ve searched though my meeting transcript & now I want to see/hear that important part in the original video.

The method I’m using to grab the transcript text actually grabs a LOT of information that’s thrown into an object being called ‘t”:

I was only using t.eventData.text to build my transcript. What do you need in order to create a jump-to-this-timecode URL for a Stream video? I had no idea! Luckily, MS supplied an easy answer — if you share a video, one of the options is to start the video at a specific time. If you pass in “st” (which I assume stands for ‘start time’) and the number of seconds ( (17 * 60) + 39 = 1059, so the 17:39 from my video matches up with 1059 seconds in the st)

We still need the unique ID assigned to the video, but … I’m exporting the transcript from MS’s Stream site, which includes the ID in the URL. So I’m able to use window.location.href to get the URL, then strip everything past the ? … now I’ve got a way to create timecoded links to video content. I just need to glom that into the code I am using to export the transcript.

Question is … how to display it to the user? Clicking on a link for 1059 seconds doesn’t really mean anything. If I were doing this at work, I might pass the number of seconds through a “pretty time” function to convert that number of seconds back into hour:minute:second format so the user clicks on 17:39 … but, as a quick example, this builds hyperlinks with the integer number of seconds as text:

var strURL = window.location.href;
strURLBase = strURL.substring(0, strURL.indexOf('?'));

var arrayTranscriptionLines = window.angular.element(window.document.querySelectorAll('.transcript-list')).scope().$ctrl.transcriptLines.map((t) => { 
	var strTimecodeURL = '<a href="' + strURLBase + '?st=' + t.startSeconds + '">' + t.startSeconds + '</a>'
	return strTimecodeURL + "&nbps;&nbps;&nbps;&nbps;" + t.eventData.text;
});
console.log(arrayTranscriptionLines);

I might also just link the transcript text to the appropriate URL. Then clicking on the text “I want you to remember this” would jump you to the right place in the video where that line occurs:

var strURL = window.location.href;
strURLBase = strURL.substring(0, strURL.indexOf('?'));

var arrayTranscriptionLines = window.angular.element(window.document.querySelectorAll('.transcript-list')).scope().$ctrl.transcriptLines.map((t) => { 
	var strResult = '<a href="' + strURLBase + '?st=' + t.startSeconds + '">' + t.eventData.text + '</a>'
	return strResult;
});
console.log(arrayTranscriptionLines);

And we’ve got hyperlinked text that jumps to the right spot:

Fortify on Demand Remediation — Cross-Site Scripting DOM (JS)

This vulnerability occurs when you accept user input or gather input from a AJAX call to another web site and then use that input in output. The solution is to sanitize the input, but Fortify on Demand seems to object strenuously to setting innerHTML … so filtering alone may not be sufficient depending on how you subsequently use the data.

To sanitize a string in JavaScript, use a function like this:

/**
 * Sanitize and encode all HTML in a string
 * @param  {string} str  The input string
 * @return {string} –    The sanitized string
 */
 var sanitizeHTML = function (str) {
    return str.replace(/&/g‘&amp;’).replace(/</g‘&lt;’).replace(/>/g‘&gt;’);
};

This will replace ampersands and the < and > from potential HTML tags with the HTML-encoded equivalents. To avoid using innerHTML, you might need to get a little creative. In many cases, I have a span where the results are displayed. I color-code the results based on success/failure … in that case, I an replace innerHTML with a combination of setting the css color style element to ‘green’ or ‘red’ then setting the innerText to my message string.

I can bold an entire element using a similar method. Changing some of the text, however … I haven’t come up with anything other than breaking the message into multiple HTML elements. E.g. a span for “msgStart”, one for “msgMiddle”, and one for “msgEnd” – I can then bold “msgMiddle” and set innerText for all three elements.

Fortify on Demand Remediation – Password Management: Hardcoded Password and Privacy Violation

These two vulnerabilities occur in the obvious case — you’ve hard coded a password or some sort of private info (e.g. SSN) and then printed it out to the browser. Don’t do that! But it also seems to occur quite frequently when Fortify on Demand doesn’t like your variable name. As an example, I have a string that provides a consistent error message when user authentication fails.

I then print the string to the screen when the user’s logon fails. Fortify says I am disclosing the user’s password. I’m obviously not. Simply renaming the variable sorts it. Now … yes, this is silly. But it’s a lot easier than trying to convince someone in Security to manually review the code, acknowledge that something about a bad password error is a totally reasonable (and descriptive) variable name, and add an exception for your code. Since bad password is error 49, I just used that in the now less descriptive variable name [ (1) Not too many people know the LDAP error codes off the top of their head, and (2) there are actually a handful of ldap bind return codes that will print this error].

Fortify on Demand Remediation – JSON Injection

This vulnerability occurs when you write unvalidated input to JSON. A common scenario would be using an Ajax call to pass a string of data to a file and then decoding that string to JSON within the file.

To get around the Foritfy scanning requirements you have to use base64 encoding on the string before sending it through the Ajax call:

var update = $.ajax({
    type: "POST",
    url: "SFPNotesUpdate/../php/validate_notes.php",
    data: { tableData: btoa(JSON.stringify(HotRegisterer.getInstance('myhot').getData())) },
    dataType: 'json'
});

When reading the input to decode the JSON string to an array you have to perform these actions in order:

  • base64_decode the input string
  • sanitize the input string
  • decode the JSON string to an array
$tbl_data = json_decode(filter_var(base64_decode($_POST['tableData']), FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES), true);

Fortify on Demand Remediation – Introduction

The company for which I work signed a contract with some vendor for cloud-based static code analysis. We ran our biggest project through it and saw just shy of ten thousand vulnerabilities. Now … when an application sits out on the Internet, I get that a million people are going to try to exploit whatever they can in order to compromise your site. When the app is only available internally? I fully support firing anyone who plays hacker against their employer’s tools. When a tool is an automation that no one can access outside of the local host? Lazy, insecure code isn’t anywhere near the same problem it is for user-accessible sites. But the policy is the policy, so any code that gets deployed needs to pass the scan — which means no vulnerabilities identified.

Some vulnerabilities have obvious solutions — SQL injection is one. It’s a commonly known problem — a techy joke is that you’ll name your kid “SomeName’;DROP TABLE STUDENTS; … and most database platforms support parameterized statements to mitigate the vulnerability.

Some vulnerabilities are really a “don’t do that!” problem — as an example, we were updating the server and had a page with info(); on it. Don’t do that! I had some error_log lines that output user info that would be called when the process failed (“Failed to add ecckt $iCircuitID to work order $iWorkOrderID for user $strUserID with $curlError from the web server and $curlRepsonse from the web service”). I liked having the log in place so, when a user rang up with a problem, I had the info available to see what went wrong. The expedient thing to do here, though, was just comment those error_log lines out. I can uncomment the line and have the user try it again. Then checkout back to the commented out iteration of the file when we’re done troubleshooting.

Some, though … static code analysis tools don’t always understand that a problem is sorted when the solution doesn’t match one of their list of ‘approved’ methods. I liken this to early MS MCSE tests — there was a pseudo-GUI that asked you to share out a printer from a server. You had to click the exact right series of places in the pseudo-GUI to answer the question correctly. Shortcut keys were not implemented. Command line solutions were wrong.

So I’ve started documenting the solutions we find that pass the Fortify on Demand scan for everything identified in our scans — hopefully letting the next teams that use the static scanner avoid the trial-and-error we’ve gone through to find an acceptable solution.

JQuery – Finding a set of checkboxes

A corollary to my JavaScript modifying checkbox values when the box is checked or unchecked … I needed a way to reset the form (in my form, the default is for the boxes to be checked and the value to be 1). The following code identifies all checkboxes with a particular class, checks them, and sets the value to 1.

/**
 * This function checks off each checkbox of the input class
 *
 * @param {string} strCheckboxClass     Name of class identifying in-scope checkboxes
 * @return {null} 
 *
 * @example
 *
 *     checkAllDatabases ('MyBoxes');
 */
 function checkAllDatabases(strCheckboxClass){
    arrayCheckboxes = $('.'+strCheckboxClass);
    for(i = 0; i < arrayCheckboxes.length; i++) {
        $( '#'+arrayCheckboxes[i].name).prop( "checked", true );
        $( '#'+arrayCheckboxes[i].name).val(1);
    } 
}

Changing checkbox value when (un)checked

This bit of code handles another rather esoteric scenario — I have a generic “go to this URL and download the resultant Excel file” JavaScript function. This is because I write a lot of reporting tools and didn’t want to write a lot of code for each new tool. The template is an input form with a submit button that calls the generic function. Params for the elements on the form from which values are read, the URL to call to generate the report, and the POST elements into which each corresponding form value is inserted gets stuffed. Works great for text inputs. Works fine for drop-downs. But the value of a checkbox is really a combination of the potential value (from the value tag) and the checked state. That is — my Button 1 has a potential value of 1, but if the box is checked or not is really important.

Instead of attempting to determine the type of element in each form input so I can evaluate the checked condition, I decided to just change the value when the checkbox state is changed. Now Button 1 has a potential value of 0 when unchecked and a potential value of 1 when checked. I don’t need to know if the box is checked because the value answers that question. So passing along button1’s value to my URL lets the target site know if I want whatever Button 1 represents. (In this case, users are able to select from a list of seven data sources — smaller numbers of data sources reduce the query time but also fail to provide the most robust report).

The JavaScript to handle changing the checkbox value when the checked state changes:

$("#button1").change(function () {
    if ($("#button1").is(':checked')) {
        $("#button1").val(1);
    }
    else{
        $("#button1").val(0);
    }
});

$("#button2").change(function () {
    if ($("#button2").is(':checked')) {
        $("#button2").val(1);
    }
    else{
        $("#button2").val(0);
    }
});

The HTML defining these two checkboxes:

<input type="checkbox" id="button1" name="button1" value="1" checked><label for="ngmss">Thing 1</label>
<input type="checkbox" id="button2" name="button2" value="1" checked><label for="ngmss">Thing 2</label>

Updating JQuery

We’ve got to upgrade some Javascript modules at work — JQuery, Bootstrap, etc. Problem is that changes between the versions mean there’s a lot of rewriting required before we can update. And we pull in these modules using a shared header file. While we could stage all of the changes and update the entire website at once … that means we’re all dedicated to updating our components & are delaying the update until we’re finished.

That’s not ideal — and has the potential to break a lot of things at once. I plan, instead, of putting a default version in the shared header file. And some mechanism to source in a newer version by setting a variable in the individual tool’s PHP code before the header is pulled in. So each tool within the site has a $strJQueryRev, $strBootstrapRev, etc variable. Then the shared header file looks for that variable — loads a newer version when requested or loads the currently used older version when no version is indicated.

if($strJQueryRev == "3.5.1"){
 echo "<script src=\"https://code.jquery.com/jquery-3.5.1.min.js\">\n";   
}
elseif($strJQueryRev == "3.1.1"){
 echo "<script src=\"https://code.jquery.com/jquery-3.1.1.min.js\">\n";   
}
else{
 echo "<script src=\"https://code.jquery.com/jquery-2.2.4.min.js\">\n";        # Old, in use, version is default
}

Or even

if(!$strRevisionNumber){$strRevisionNumber="2.2.4";}
echo "<script src=\"https://code.jquery.com/jquery-$strRevisionNumber.min.js\">

Each developer can add a version number to a single tool, test it, push it up through production using the newest modules. Move on to the next tool. The site still isn’t done until we’re all done, but we can slowly roll out the update as people are able to test their tools.

Exporting A Microsoft Teams Chat

There’s no export functionality in MS Teams chats and conversations. From Microsoft’s standpoint, this makes sense — customer retention. From the customer standpoint, however? There are times I really want to transfer a conversation elsewhere for some reason. You can copy/paste individual text bubbles. If you only need to get one or two bubbles, manually copying the text is going to be quicker. And, for those with special access, there’s the Security & Compliance discovery export stuff as well as an approach using the Graph API. But for the rest of us general users, there’s no easy way to export the bunch of little chat bubbles that comprise a MS Teams chat.  There is, however, a not-too-hard way to do it in the Teams web client.

I’ll prefix these instructions with a disclaimer – your company may have document retention in Teams. When you export your chat content, you’ll need to maintain appropriate retention policies yourself. In IT, we had a few information categories where retention was “useful life” – we could retain system documentation as long as the system was used. If you’re exporting a chat to keep something you are allowed to keep and then keep it outside of Teams … that’s awesome. If you are trying to keep something the company’s retention policy says should be removed … that’s probably not awesome.

Once you’ve determined that the info you are exporting is OK to export and maintain elsewhere, here’s how to export a Teams chat from within the Teams web client. Step 1, of course, is to lot into Teams at https://teams.microsoft.com and go to the chat you want to export. Scroll up to the top of the chat. If you have a really long chat, it may not be possible to export the entire thing using this approach. I might play around with it in the future, by most of my conversations are in Teams channels so I don’t have a chat that’s more than 30 or so messages.

Once you are at the top of the chat, open the developer tools (ctrl-shift-i in Chrome). Clear the errors — they clutter up the screen.

Paste the following script into the console and hit enter:

var strRunningText = "";
var collectionMessageBubbles = document.querySelectorAll('.message-body-content, .message-datetime');

for (let objMessageBubble of collectionMessageBubbles) {
     strRunningText = strRunningText + "\n" + objMessageBubble.textContent;
}

console.log(strRunningText);

If you have a long series of chat messages, you’ll get some of the chat displayed and a button to copy the entire chat content to your clipboard.

If you have a shorter series of chat messages, you’ll have the text of the chat in the console window. You can highlight it and copy/paste the text elsewhere.

There’s a little cleanup that can be done – the content of the message-datetime elements have a beginning and trailing newline character along with a bunch of whitespace. You can get a cleaner timestamp (but, if you embed code within your messages … which I do … the code sections have a lot of extraneous newlines):

var strRunningText = "";
var collectionMessageBubbles = document.querySelectorAll('.message-body-content, .message-datetime');

for (let objMessageBubble of collectionMessageBubbles) {
     strRunningText = strRunningText + "\n" + objMessageBubble.innerText;
}

console.log(strRunningText);

The same JavaScript works in the Teams channel conversations except the channel conversations tend to be longer … so you’re going to export some subset of the channel conversation around where you are in the web browser.

* I realized, during a multi-person chat last week, that I don’t grab the name of the individual who posted the message to the chat. Grabbing the person’s name should just entail adding the identifier for the name element into the querySelectorAll list … but that’s not something I’ve had an opportunity to check yet.