NTLM login with Anonymous fallback

At work we run a fairly large extranet SharePoint farm where some sites are anonymous access. Normally in SharePoint, when a domain user on a domain workstation hits a SharePoint site that requires authentication, it automatically logs them in. Unfortunately, in that same scenario but the site is anonymous, the user will not get logged in. The user then has to realize this and click sign in at the top right. Also because SharePoint security trims the user interface so you only see what you have access to see.  Many times users will complain they have lost their permissions or something because they won’t be able to find their normal way of doing things when they don’t realize they aren’t logged in.

Several years ago I set out on a mission to fix this. The best solution I found was posted here: http://blogs.claritycon.com/blogs/ryan_powers/archive/2007/06/12/3187.aspx. I was able to take what he had done and modify it for SharePoint. So since authentication is tied to the session, we only need to run our code once per session. So the trick is determining when a new session is starting and running the code. I was unsuccessful in getting the SessionStateModule.Start event to fire and not fire when I needed it to so I decided to use the PostAcquireRequestState event and check on each request if I have already run my code or not. I’m using a session variable to ensure I only run the code once per session. Essentially what the code does is the first time a new session is started instead of outputting the normal html requested, the httpmodule outputs some javascript.

This javascript makes a web call to /_layouts/authenticate.aspx which is the page used to login you into SharePoint. This call is done in a way so if an error happens (such as not being able to login) that error is trapped and the end user never sees the gray login box. Whether the login is successful or not the javascript then refreshes the page. At this point the httpmodule will not change the html output because it’s already been run on the current session so the real requested html is sent and if the authentication was successful, the user is shown as being logged into the site.

Also notice I added the querystring Source=%2F%5Flayouts%2Fimages%2Fblank%2Egif to the authenticate url. Normally when authenticate.aspx is called without this querystring it will redirect the user to the current site’s homepage. Since this causes extra execution time running webparts and rendering the page as well as the end user never sees this page because the call is being done in javascript, I found it was much faster to send the user to an image after authenticating. So i chose the blank.gif which is available on any SharePoint installation. This requires much less resources on the server as well as showing the actual page faster to the browser.

I also do a few checks before I output the javascript. Since automatic ntlm login only works in IE and windows, I check the useragent for that condition. I also check to make sure the request isn’t for infopath form services (/_layouts/formserver.aspx) because we had some issues with the module not playing nice with those services.

Anyway, here’s the code:

public class MixedAuthenticationScreeningModule : IHttpModule
{
    private const string NO_SCRIPT_MESSAGE = "Your browser does not support JavaScript or has scripting disabled, which prevents credentials screening from working.";
    private string _requiresAuthenticationUrl = "/_layouts/Authenticate.aspx?Source=%2F%5Flayouts%2Fimages%2Fblank%2Egif";
 
    void IHttpModule.Init(HttpApplication context)
    {
        context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
    }
 
    void context_PostAcquireRequestState(object sender, EventArgs e)
    {
        MixedModeLogin();
    }
 
    private void MixedModeLogin()
    {
        HttpContext context = HttpContext.Current;
        if (context.Session == null) return;
        if (context.Request.RequestType != "GET") return;
        if (context.Session["MixedModeAuth"] != null) return;
        context.Session["MixedModeAuth"] = false;
        if (IsWin32Ie(context) == false) return;
        if (context.Request.RawUrl.ToLower().Contains("/_layouts/formserver.aspx")) return;
        context.Session["MixedModeAuth"] = true;
        RenderScreeningHtml(context.Request.RawUrl, context.Response);
    }
 
    private bool IsWin32Ie(HttpContext context)
    {
        string userAgent = context.Request.UserAgent;
        return userAgent != null && userAgent.IndexOf("MSIE") >= 0 && userAgent.IndexOf("Windows") >= 0;
    }
 
    private void RenderScreeningHtml(string currentUrl, HttpResponse response)
    {
        string screeningFailedUrl = currentUrl;
        response.Cache.SetCacheability(HttpCacheability.NoCache);
        response.Cache.SetExpires(DateTime.Now.AddDays(-1)); //or a date much earlier than current time
        response.Write("<!--DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"--><html></html><head></head><script type="text/javascript" language="javascript">  function canAuthenticate() { try { var dom = new ActiveXObject("Msxml2.DOMDocument");  dom.async = false; dom.load("" + _requiresAuthenticationUrl + "");} catch(e) { return false; }  return true;}  canAuthenticate(); window.location.href=window.location.href; </script><noscript></noscript>");
        try
        {
            response.End();
        }
        catch { }
    }
 
    void IHttpModule.Dispose()
    {
    }
}

Here is what the user’s see when the javascript code is trying to log them in:

Here is what shows up in fiddler when someone is logged in successfully. Notice the first time default.aspx is called it’s only 763 bytes and the second time 61 KB. That’s because the first time only the javascript was sent to the browser, but the second time the whole html for the page was sent to the browser. You can also see where the authentication happens and since it was successful you can see where the blank.gif was loaded.

 

Here is what shows up in fiddler when someone is not logged in successfully, thus it falls back to anonymous. You can see that the authentication doesn’t happen and blank.gif is never called.

PowerPoint 2010 Save As Web Page

Recently a need came up for a user that wanted to post a PowerPoint slide with links embedded in it onto a SharePoint 2007 teamsite. Unfortunately, in Office 2010 the ability to Save As Web Page doesn’t exist anymore. It seems they are really promoting their PowerPoint services (Office Web Apps) and decided to remove this capability from the user interface. Fortunately, the backend code is still there to perform this function.

I found this forum post: http://social.technet.microsoft.com/Forums/en/officeappcompat/thread/89d70894-b455-4d3e-a801-f2574c3a0f5a talking about a quick way to save as html through the visual basic editor in PowerPoint. This was good but not very user friendly.

I decided to write my own PowerPoint Add-In to add a button in the ribbon that saves the presentation to html. Since this is my first add-in, it did take me a little while to get it working but I was really happy with the development experience.  Hitting F5 compiled, built and deployed my add-in and then started powerpoint for me.  This made debuging and testing really quick.

To get started I fired up Visual Studio 2010 and created a new PowerPoint 2010 Add-In. I then added a new item and selected Ribbon. I also added a button to the ribbon from the toolbox and created an on-click event for it. Below is the code for the on-click event:

SaveFileDialog saveFileDialog1 = new SaveFileDialog();

saveFileDialog1.Filter = "htm files (*.htm)|*.htm|All files (*.*)|*.*";
saveFileDialog1.FilterIndex = 0;
saveFileDialog1.RestoreDirectory = true;
saveFileDialog1.AddExtension = true;
saveFileDialog1.AutoUpgradeEnabled = true;
saveFileDialog1.CheckFileExists = false;
saveFileDialog1.CheckPathExists = true;
saveFileDialog1.DefaultExt = "htm";

if (saveFileDialog1.ShowDialog() != DialogResult.OK) return;

string filepath = saveFileDialog1.FileName;

Globals.SaveToHtml.Application.ActivePresentation.SaveAs(filepath, Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsHTML, Microsoft.Office.Core.MsoTriState.msoFalse);

 

Here’s a screenshot of what it looks like:

 

One of the reasons it took me a while was I had issues getting it installed on another machine.  It turns out I didn’t select Microsoft Visual Studio 2010 Tools for Office Runtime as a prerequisite to install under the publish settings.  Once that was checked it installed fine on other machines.