DotNetNuke Tips & Tricks

Thursday, December 11, 2008 by Ian Robinson

"Improving the Secured File UX" Part Two

Filed under: Tips & Tricks

In Improving the Secured File Download UX for Unauthenticated Users I elaborated on a workaround hack to display a friendly error message and redirect to the login page when trying to access a file that had been through DotNetNuke's file ticket system.

If you're in a situation where that solution makes sense for you - great. But what about future releases of the application? Should this functionality exist within the framework itself? Is it too trivial? Does it make sense for everyone that uses the framework so much so that it should be a part of it?

To find out, I put the issue in DotNetNuke's Gemini.

I suggest that when you encounter a workaround/hack/enhancement such as this:

  • Don't just fix it for now
  • These "fringe areas" are one of the places where the "community at large" can be of most help. Let the core team focus on the big stuff.
  • So...put it in Gemini!
  • And, ideally, try to give the core team a head start.

So, in an effort to follow the above path, I cracked open my handy DotNetNuke source code and made a few changes.

update: I'm providing the following information for reference only, I really don't think you should make this change to your DNN application. My intention is just to remind you that the core code is there, we can easily make changes, test them out, and suggest to the DotNetNuke team that they incorporate those changes into the application.

Background

When the user requests the file, the LinkClick.aspx "page" is requested. (e.g. "http://localhost/DotNetNuke_2/LinkClick.aspx?fileticket=XKDsP6pHvtQ=&tabid=36&mid=410"). This really isn't a page, but is a handler registered in the application's web.config.

The actual class that handles the file request is called "FileServerHandler" and is found in Library\Components\FileSystem\FileServerHandler.vb in version 4.9 and \Library\Services\FileSystem\FileServerHandler.vb in DotNetNuke 5.0 RC2.

While some slight refactoring has been done to this class between 4.9 and 5.0, the portion we're concerned with has not changed:

' serve the file
If TabId = Null.NullInteger Then
    If Not FileSystemUtils.DownloadFile(_portalSettings.PortalId, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        context.Response.Write(Services.Localization.Localization.GetString("FilePermission.Error"))
    End If
Else
    If Not FileSystemUtils.DownloadFile(_portalSettings, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        context.Response.Write(Services.Localization.Localization.GetString("FilePermission.Error"))
    End If
End If

The above code is responsible for the original behavior of showing the localized file permission error message.

Goals for Improvement

If the user is not able to download the file:

  1. If they are not logged in, let's take them to the current portal's login URL with a return URL for the file ticket.
  2. If they are logged in, display the localized FilePermission.Error message, as usual.

Adding a Utility Method

First, let's add a method to help us get the current portal's login URL:

Private Function GetLoginUrl(ByVal portalSettings As PortalSettings, ByVal request As HttpRequest) As String
    If Not portalSettings Is Nothing AndAlso Not request Is Nothing Then
        Dim tabId As Integer = portalSettings.ActiveTab.TabID
        Dim controlKey As String = "Login"
        If Not Null.IsNull(portalSettings.LoginTabId) AndAlso String.IsNullOrEmpty(request.QueryString("override")) Then
            controlKey = String.Empty
            tabId = portalSettings.LoginTabId
        ElseIf Not Null.IsNull(portalSettings.HomeTabId) Then
            tabId = portalSettings.HomeTabId
        End If
        Return DotNetNuke.Common.Globals.NavigateURL(tabId, controlKey)
    Else
        Throw New ArgumentNullException(If(portalSettings Is Nothing, "portalSettings", "request"))
    End If
End Function

This method is intended to yield the correct login page whether a custom one is specified or not. It could potentially use the current tab, or the home tab as well. Just playing it safe.

Adding the core enhancement

Now, we need to figure out whether or not the user is logged in, and perform one of two actions. Either redirect them to the login page, or let them know that they don't have sufficient privileges to view the file.

Private Sub RedirectToLoginOrDisplayErrorMessage(ByVal context As HttpContext, ByVal _portalSettings As PortalSettings)
    If (context.Request.IsAuthenticated = False) Then
        Dim loginPage As String = GetLoginUrl(_portalSettings, context.Request)
        Dim returnUrl As String = HttpUtility.UrlEncode(HttpContext.Current.Request.Url.PathAndQuery)
        context.Response.Redirect(loginPage + "?returnurl=" + returnUrl)
    Else
        context.Response.Write(Services.Localization.Localization.GetString("FilePermission.Error"))
    End If
End Sub

Wiring it Up

Now, without changing the original code much, we can replace a couple lines of code, set a few break points, and try this thing out.

' serve the file
If TabId = Null.NullInteger Then
    If Not FileSystemUtils.DownloadFile(_portalSettings.PortalId, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        RedirectToLoginOrDisplayErrorMessage(context, _portalSettings)
    End If
Else
    If Not FileSystemUtils.DownloadFile(_portalSettings, Integer.Parse(UrlUtils.GetParameterValue(URL)), blnClientCache, blnForceDownload) Then
        RedirectToLoginOrDisplayErrorMessage(context, _portalSettings)
    End If
End If

Summary

Mission accomplished. Now to see if it gets picked up anywhere along the way. Let me know what you think of this enhancement or the process in general. Also, I did enjoy picking apart this one trivial thing, so if you have any suggestions for future experiments, please let me know.

Comments

Joe Craig
Friday, December 12, 2008 8:58 AM
We created a LinkClickHandler that redirects to a user configurable page if the user doesn't have permission to download the file.

Installation requires copying a dll into the bin directory and a couple of changes in web.config. No core changes needed.

If this is of general interest I might be able to make this available to the community.
Ian Robinson
Friday, December 12, 2008 9:05 AM
Joe,

I don't suggest that others make changes to the core. The post is just a walk-through of the process that I went through when submitting some new code to the DotNetNuke team in Gemini.

Glad to hear you have a solution as well, if you wanted to make that available to the community, I will be sure to post a link here. In the event that you do, there will be two available solutions to the community for current versions of DotNetNuke (my previous javascript/html solution and your updated handler).

Again, I don't recommend modifying the core for this, I just wanted to set a good example for the community by incrementally making the DotNetNuke core better.

Take care,

Ian
blog comments powered by Disqus