Thursday, December 30, 2010

Integrating SalesForce.com with SumTotal LMS

Recently I obtained a developer’s license for SalesForce to play with and learn more about this environment. One of the things that I wanted to figure out in my playing was to integrate another web application as a tab into the SalesForce interface. I thought that it would be fun to figure out how to link our learning management system (LMS) up with SalesForce. We are using a LMS from SumTotal which is a pretty flexible application.

For those that are not familiar with what a LMS is, it is an application that provides an environment for keeping track of learning activities for users and many can also provide on-line learning content.
I will walk through much of the setup that I went through to get this to work though there are certain elements that I will not show for security reasons.

First off you would need to setup the remote application, in our case the LMS, to be able to receive a pass through authentication from SalesForce. For the SumTotal application I decided to use the MD5 pass through process. For simplicity of administration I decided to implement the authentication process a little differently than most. Most people that I have talked to will create the pass through authentication page and put it into the same web application as the LMS. There are two challenges with this.

I discovered the first of these challenges recently. This is how I had implemented the process initially though we just went through an upgrade of the application and this because a problem. I recommend NOT putting the pass through authentication pages in the same web application as the core LMS application.

The second issue is that the SumTotal web application is a precompiled J# application and putting uncompiled web pages into the same application can be tricky (not impossible, but tricky). For those who are not familiar with web development this can be a bit of a pain.

So with these two issues in mind, I took this approach. First of all we have a defined web instance (not the default instance) in IIS (Internet Information Services) dedicated to the LMS. There are a couple of virtual web folders, or web application, for the core application and its web services in the web site. I created two new virtual folders, or web applications, in the website. One for SSO pass through authentication and one for content that we would like to have exposed to the general public without going through the LMS (not important for this discussion). The SSO web application is not locked down since we want anyone to be able to access the pages to log in.

I created a script specifically to use with SalesForce (I do not like to mix authentication pages between different login processes). For simplicity I used J# to write the script though since I created my own web application for SSO I could have used any language. Below is a sample of the code that I used though you would need to modify a few things for it to work in your environment; css links and URLs. Also as a note I try to typically lock down the script so that it will only accept calls from certain web pages (referrers). Unfortunately when adding this call to SalesForce it makes the call without a referrer (similar to typing in the URL directly in the address line in your browser). This leaves a little bit of a security risk for your LMS which I have not figured out yet. The risk is that anyone who figures out the SSO URL could hack their way into the application (this is why I like to do referrer filtering). The MD5 authentication does not take a password for authentication. It assumes that authentication has already been performed in the source application and “trusts” the value being sent.

<%@ Page Language="VJ#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
public String user;
private void getUserName(Object obj, System.EventArgs e)
{
// Load NameValueCollection object.
NameValueCollection coll = get_Request().get_QueryString();
String test = coll.get_Item("username");
String Name = coll.get_Item("username");
String username = Name;
user = username;
rtUserID.set_Value(username);
String validreferrer1 = "";
int validreferrer = -1;
String errorurl = "/sumtotal/app/SilentLogon_Error.aspx?UserName=" + user + "&SilentLogonError=-5";
try
{
Uri referrerUrl = get_Request().get_UrlReferrer();
String httpreferrer = referrerUrl.get_AbsoluteUri().ToString();
get_Response().Write(httpreferrer);
if(httpreferrer.indexOf(validreferrer1) > -1)
{
validreferrer=1;
}
if(validreferrer < 0)
{
get_Response().Redirect(errorurl);
}
}
catch (Throwable t)
{
//get_Response().Redirect(errorurl);
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<title> New Document </title>
<meta name="Generator" content="Microsoft FrontPage 4.0" />
<script type="text/javascript" language="JavaScript" src="md5.js"></script>
<meta name="Author" content="" />
<meta name="Keywords" content="" />
<meta name="Description" content="" />
<link rel='stylesheet' type='text/css' href='/SumTotal/client/media/css/default_800/SYS_base.css' />
<link rel='stylesheet' type='text/css' href='/SumTotal/client/media/css/default_800/Custom_SYS_base.css' />
</head>
<body onload='content_onload()'>
<script type="text/javascript" language="JavaScript">
//<!--
function getAuthCode()
{
var strEmp = String(document.getElementById('rtUserID').value);
var strtime_stampStamp = String(document.getElementById('time_stamp').value);
var strKey = String(document.getElementById('MD5Key').value);
document.getElementById('auth_token').value=hex_md5(strEmp+strtime_stampStamp+strKey);
}
function content_onload()
{
var dt = new Date();
document.getElementById('time_stamp').value= dt.getUTCFullYear() + ":" + (dt.getUTCMonth()+1) + ":" + dt.getUTCDate() + ":" + dt.getUTCHours () + ":" + dt.getUTCMinutes() + ":" + dt.getUTCSeconds();
getAuthCode();
document.getElementById('frmLogon').submit();
}
//-->
</script>
<form onload="getUserName" id="frmLogon" runat="server" method="post" action="/SumTotal/app/SYS_login.aspx">
<input type="hidden" runat="server" id="rtUserID" name='rtUserID' />
<input type="hidden" runat="server" id="time_stamp" name="time_stamp" value="2003:07:10:09:30:05" />
<input type="hidden" runat="server" id="MD5Key" name="MD5Key" value="12345" />&nbsp;
<input type="hidden" runat="server" id="auth_token" name="auth_token" />
<table width='100%' cellpadding="0" cellspacing="0" border="0">
<tr>
<td id='mainLeftTD' class='clsPageHeaderLeft' align=left valign=center nowrap>
<table>
<tr>
<td align=left valign=center nowrap>&nbsp;
<image border=0 src='/SumTotal/client/media/images/default_dlx_800/page_header_logo.gif' alt=''>
</td>
</tr>
</table>
</td>
<td id='mainRightTD' class='clsPageHeaderRight' style='background-image: url(/SumTotal/client/media/images/default_dlx_800/page_header_background.gif)' cellpadding=0 cellspacing=0 border=0>
</td>
</tr>
</table>
<br />
<br />
<br />
<br />
<table border="0" width="100%">
<tr>
<td>&nbsp;</td>
<td></td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
</tr>
<tr>
<td>&nbsp;</td>
<td></td>
</tr>
<tr>
<td colspan="2" align="center">
<font size="4"><b>You are now being logged into the SumTotal LMS as <%=user%>.... <i>Please wait</i></b></font>
</td>
</tr>
<tr>
<td align="center">
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<font size="1"><i>If you are not redirected in 5 seconds please press the button below.</i></font>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="Log On" name="btnToLogonPage" onclick='getAuthCode()' />
</td>
</tr>
</table>
</form>
</body>
</html>
Now that you have the SSO script in place you will need to expose the LMS to the outside world. If you are using the hosted version of the application it should already be exposed though I am not sure how to setup custom authentication paths using that model. Our LMS is an on-site installation. There are many methods for exposing a website to the outside world which is beyond the topic of this article.

For this exercise I created a test domain and test account to play with so that I do not impact any live accounts.

That is all that is needed on the LMS side of things though I recommend testing the URL to the new pass through page to make sure that it works prior to going to the SalesForce setup.

If you have not already created a developer account in SalesForce I recommend it. You can go to http://developer.force.com/ to create the account. When you create an account you will get a blank SalesForce site to play with (in the screenshots my site will have miscellaneous items in it due to may playing around in the site prior to this article).

First off you will need to decide what data element in the user profile to use that will match the user ID in the LMS. In the dropdown next to your login name you will see an option for “setup”. Select that option and the setup for the site will come up. Close to the bottom of the left hand side under “Administration Setup” there is an option called “Manage Users”. Open that up and select “Users”. Click on the user in the right hand side that you want to test with. There are many options that can be used. I selected to use the “Employee Number” option. The value in that field will match the user id that I setup in the LMS, in this case it is “testuser”.

clip_image002

Now that you have a user value that matches something in the LMS it is time to add the link in SalesForce. While still in “setup” on the left hand side go to the “App Setup” section, open the “create” menu and select “Tabs”. Mine looks like the below image though yours may be blank depending upon if you have added anything as of yet.

clip_image004

Click on the “New” button under the “Web Tabs”. A wizard will come up to help you through the tab creation process.

The first page in the wizard is below. You can use either format. I am going to use the “full page width” option because I think it looks better. Select “Next” after you have made your selection.

clip_image006

On the next wizard screen there are a lot of options that control how the linked object is displayed within the SalesForce environment. First give your new tab a Label and a Name. Select a tab style (color and icon) that you want to be associated to it (I selected the “Presenter” style). By default the “height” of the tab is set to 600. This controls whether the scroll bar will be displayed depending upon how big the page is. We try to have things in 800 pixel size so I entered 800. I have not tested the LMS with a mobile device so I am not sure if it is “mobile ready”. I do not have any splash pages defined so I do not need to select anything here. Lastly add a description. This is best practice to always add descriptions to your objects. Click “Next” when you are done.

clip_image008

The next wizard screen is where you add the URL link and pass the values that the application will need. The two dropdowns are there just for ease of use in finding the right syntax to use to pass the right value from SalesForce to the application. I put my URL in the URL box (you will note that my actual URL in this screen is not visible. You would need to have your actual URL to put in this space. One note is that you are only able to pass values via the query string. After you are done with your URL string press the “Next” button.

clip_image010

The next two screens control who can use the new tab and what applications it is available under. Once you click “Save” on the last tab you will be brought back to the main page that shows all the tabs in the application. You should also see the new tab at the top of the SalesForce interface.

clip_image012

Now if I go and click on the LMS tab in the SalesForce interface I will now see the pass through page for a moment and then it will forward on to the home page for the user.

clip_image014

Monday, December 27, 2010

Interesting Flash issue

Recently I came across an interesting issue with an application that uses flash.  My son received a LeapFrag MyPal as a present for Christmas.  There is an application that you can download from their site to personalize the toy to your child.  I downloaded the application and it installed without error.  It was a 32 bit application and installed in the right environment.  When the application started I received a prompt that the correct version of flash was not installed and it prompted me to download and install Flash which ends up erroring out.  I verified that the version of Flash that the application wants is the same version that is on my machine (10.1.102.64).  Just to make sure that I was not missing anything I installed Flash from Adobe's website and it installed fine (or more appropriately verified that I have the most recent version installed).

At this point I was very frustrated with the install until I started to think about the environment that I was working in.  I am running a 64 bit version of Windows 7 and Flash is a 32 bit application and as such Flash is installed in the path C:\Windows\SysWOW64\Macromed\Flash\.  I made a guess that the application is probably doing a version check of the Flash binary using a hard coded path to the default installation path.  In most situations this is C:\Windows\System32\Macromed\Flash\.  In a Windows 64 bix OS all the 64 binaries are stored in the C:\Windows\System32 folder (even though this sounds a bit odd) so flash would not be installed at this location.

So even though I know that the Flash binaries would not work in the C:\Windows\System32 folder in order to get the application to get past this binary version check I copied the Macromed folder and its contents to the C:\Windows\System32 folder and now the application works and Flash still works (since it is really executing via the C:\Windows\SysWOW64\ path).

Not a pretty workaround and is, in my opinion,  a flaw in the application it works without impacting any other applications.