Restricting access to Sitecore Media Items

Posted 03 March 2015, 11:00 | by | Perma-link

I recently had a requirement to lock down some media items (PDFs in this case) within Sitecore so that only certain logged in users could access them. In principle this is trivially easy - ensure the users are in the right roles, remove read access from the extranet\anonymous user and grant read access to the specific roles. However, as always, the devil is in the details.

Whilst the above steps did work and users were correctly sent to the login page there was a problem - once the user logged in, they were just sent to the home page of site rather than being returned to the item they'd requested.

Checking the web.config I found the following setting, which defaults to false:

<setting name="Authentication.SaveRawUrl" value="true" />

But setting it to true here didn't actually make any difference - because the out of the box MediaRequestHandler ignores this value. I'm not really sure whether that makes sense at all - if I lock down some images for example, but then include them on a publicly accessible page the user isn't going to be prompted to log in, they'd just get broken images as the browser requests an image but gets HTML in response, but in the context of a PDF or other document surely you'd want to log in and be returned to the correct place.

Anyway, the solution was fairly straight forward. I created a new RestrictedMediaRequestHandler that inherits MediaRequestHandler and then overrode only the DoProcessRequest method:

/// <summary>
/// Extends the Sitecore MediaRequestHandler to include the requested
/// URL in the redirect to the login page.
/// </summary>
public class RestrictedMediaRequestHandler : MediaRequestHandler
{
  protected override bool DoProcessRequest(HttpContext context)
  {
    Assert.ArgumentNotNull(context, "context");
    MediaRequest request = MediaManager.ParseMediaRequest(context.Request);
    if (request == null) {
      return false;
    }

    Media media = MediaManager.GetMedia(request.MediaUri);
    if (media != null) {
      // We've found the media item, so send it to the user
      return DoProcessRequest(context, request, media);
    }

    using (new SecurityDisabler()) {
      // See if the media item exists but the user doesn't have access
      media = MediaManager.GetMedia(request.MediaUri);
    }

    string str;
    if (media == null) {
      // The media item doesn't exist, send the user to a 404
      str = Settings.ItemNotFoundUrl;
    } else {
      Assert.IsNotNull(Context.Site, "site");
      str = Context.Site.LoginPage != string.Empty ?
          Context.Site.LoginPage : Settings.NoAccessUrl;

      if (Settings.Authentication.SaveRawUrl) {
        var list = new List<string>(new[]
                                    {
                                        "item",
                                        Context.RawUrl
                                    });

        str = WebUtil.AddQueryString(str, list.ToArray());
      }
    }

    HttpContext.Current.Response.Redirect(str);
            
    return true;
  }
}

Then I updated the web.config to tell the sitecore media handler to use this new handler instead of the default one, and all was well in the world:

<add verb="*" path="sitecore_media.ashx"
     type="Custom.Infrastructure.Sitecore.RestrictedMediaRequestHandler, Custom.Infrastructure"
     name="Custom.RestrictedMediaRequestHandler" />

And now when a user requests a PDF they don't have access to they are sent to a login page that can return them to the PDF afterwards.

Filed under: ASP.NET

Working with Symplified

Posted 31 August 2012, 19:00 | by | Perma-link

I've been working on a couple of Proof of Concept demos for a client that's looking to implement a Single Sign On solution on their new site, and one of the offerings was from Symplified. Seeing as there doesn't appear to be much out there on this, especially within an ASP.Net context I thought I'd write up my experience.

Symplified Network Overview

The first thing to realise is that Symplified works as a reverse proxy, sitting between your server and your users (reverse in that it's a proxy you put in place rather than your user's ISP). So all requests hit the Symplified app server first before they are forwarded on to your servers. All authorisation is handled by the Symplified app, so you shouldn't be locking things down with the authorization elements in web.config files.

However, you can still use some of the features that the framework provides you with a bit of care.

Membership Provder

I started off with the idea of implementing a custom Membership Provider to handle the authentication/authorisation aspects (as this had worked well in the previous PoC based on PingFederate).

CreateUser

You can still implement the CreateUser method in a custom membership provider, as you will need to provision users within Symplified, especially if you want to allow direct registration.

In Symplified's world, you will need to make three calls to a rest service:

  1. Create a session token
  2. Create a user
  3. Reset the user's password

You need to reset the password as by default the users appear to be created with an expired password, and resetting it to the same value fixes this - note that Symplified will also send an email to the user informing them that they've reset the password - you may want to suppress this.

Not too bad, however handling errors from the create user service is a little tedious:

  • If any of the parameters don't match the patterns expected you'll get a 500 lnternal Server error returned with plain text error messages in the XML response.
  • If the user already exists you'll get a 400 Bad Request, again with the error description in the XML.

These plain text error messages will need to be parsed and mapped to MembershipCreateStatus values to get sensible errors back to your controls.

ValidateUser

You can't really implement the ValidateUser method however, as there's nothing in the API you can call to do this, the user's login credentials need to be sent directly to Symplified's SSO application so it can set it's cookies appropriately, and then pass some headers through to your "secure" areas.

So, how do you handle an authenticated user?

When the user is viewing a "secure" area of your site, Symplified will send a number of additional headers along with the request, which will include things like the Username, which can then be used to generate a Forms Authentication ticket and a Membership Priniple that you can fill for the app to use later.

For the PoC I implemented that logic in a custom Module that hooks into the application's AuthenticateRequest event.

OpenId Users

The one big issue so far has been around users authenticating via OpenId providers. These users are authenticated without a user being be provisioned in Symplified, which could well be an issue for you. The solution we put in place within the PoC was to check for the headers stating this was a login from the OpenId provider, and then attempt to create a user within Symplified, and ignoring the duplicate username error message - the Symplified engineers were looking at adapting the solution so that if the OpenId user matched known user it would send additional headers which would allow me to skip the creation step.

Next Steps

If we decide to go forward with Symplified there are a number of changes I'd like to make:

  • Only create the user context if the request comes from the Symplified app.
  • Implement the GetUser methods using the Symplified API.
  • Redirect requests to the Symplified applicance if they don't come from there.
  • Don't try and create the user on every single request!

Filed under: ASP.NET

Upgrading to ASP.NET 4.0

Posted 08 June 2010, 22:00 | by | Perma-link

Bit of a Link List this one, but it's got a few nuggets in there too.

Thanks to my excellent web hosts LiquidSix Hosting who upgraded our servers within about a week of ASP.NET 4.0 coming out, I've been able to focus on pulling this site over to the latest platform.

There were various reasons for wanting to do this, from cleaner web.config files, the new <%: %> syntax for HTML encoding output, and indeed answering some of my issues with Entity Framework, but obviously the key reason is it's new and shiney Grin

The key issue I had with it was that I forgot to update one small part of my web.config:

<system.web>
    <httpRuntime requestValidationMode="2.0"/>
</system.web>

As I'd forgotten to set this, I was unable to use any markup when editing my blogs. Thankfully the error message tells you exactly what to set, so the fix was easy.

Other tweaks that also went live this week:

  • TypeKit have recently opensourced their code, and added an events system, which means that in FireFox the Flicker of Unstyled Text should be somewhat less intrusive.
  • The launch of DoodleStatic to host my static files (JS, CSS, images) a small, cookieless domain to speed up download of the site infrastructure.
  • Changing my calls to jQuery and Typkit's core libraries to ajax.GoogleApis.com, so your browser should be able to find them in its cache where other sites have used them.
  • Thanks to Damien Gaurd for spending the time to point out how to use the System.ServiceModel.Syndication namespaces in the context of an ASP.NET MVC site, something I'd been meaning to do for some time.

And a few other minor tweaks to keep things tidy.

Filed under: ASP.NET

Adding shipping methods to a Commerce Server Basket

Posted 11 May 2009, 23:26 | by | Perma-link

This week, I have mostly been using Commerce Server. Well, to be honest it’s more like this month, but there we go. Seeing as I've only really ever managed other developers using Commerce Server, and we stuck pretty much to the "Let's try and make our business requirements line up with the starter site", this has been a fairly steep learning curve - as we're currently building a system with no inventory, nothing to deliver, and we're using a form building solution to create a heavily designed site - it's going to look great, but it might be painful getting there.

As you may have noticed from twitter, there are some things about this experience that I really don't appreciate in this day and age - most notably the whole magic string setup - although I guess that's partly due to the "extensible" nature of the system. But then there's the mixing of types - most things a user creates, such as catalogues, products, variants, discounts, etc have a string identifier, or possibly an integer if you're lucky, but then we got on to shipping methods - there aren't any, it's an online subscription, we have no stock (but for the last week all our products have been "out of stock"), so we have nothing to deliver, but I'm still getting pipeline errors because there's no shipping method associated with the basket.

So to the meat of this post: How do I associate a shipping method with a basket? I don't. I associate a shipping method with a Line Item - each and every one in the basket - and no way to say "apply this setting to all line items" - how often do you think to yourself on Amazon, "Oh, I'd like that CD to arrive tomorrow, but I don't mind if that one doesn't turn up for 5 days on their Super Saver Delivery™ option"? I know I don't, I just order stuff and have it turn up when it does, which pretty much ends up like that, as I order books that aren't out yet.

All I can say is thank you Microsoft for Object Initializers - they mean that I can at least do this:

// Get the current line items from the Commerce Sever basket
LineItemCollection currentItems = orderform.LineItems;
// Loop over the line items from the webservice call
foreach (wsLineItem wsItem in basketDetails.LineItems) {
  // See if we can find a line item in the basket that
  // matches the one from the web service
  csLineItem csItem = findLineItem(currentItems,
                                   wsItem.Catalogue,
                                   wsItem.Id,
                                   wsItem.VariationId);
  if (csItem == null) {
    /* We didn't find one, so add a new line item to the basket.
     * Here's where the magic happens - create a new line item with all
     * the properties we can set in the constructor, then add the remainder
     * through object initializers. */
     currentItems.Add(
              new csLineItem(wsItem.Catalogue,
                             wsItem.Id,
                             wsItem.VariationId,
                             wsItem.Quantity)
                             {ShippingMethodId = basketDetails.ShippingMethod});
  } else {
    // We found a match
    if (wsItem.Quantity == 0) {
      // The web service had 0 in the quantity - remove the item
      currentItems.Remove(csItem);
    } else {
      // The web service might have an updated quantity - update.
      csItem.Quantity = wsItem.Quantity;
    }
  }
}

Filed under: ASP.NET

ASP.Net AJAX and MCMS

Posted 08 July 2008, 23:15 | by | Perma-link

Ok, this one's pretty easy really, Initially we were just running with Stefan Großner's tips and code samples for using friendly URLs everywhere, and his hints on How to enable AJAX with MCMS and ASP.Net 2.0.

This was generally working fine for us, the page was requested by the ScriptManager calls, the ScriptManager in the master page hooked into the request, read the query strings it was looking for, rendered out the correct JavaScript, and stopped the page execution - all as you'd expect.

However, as development of the site progressed, we then built a control that allowed us to show or hide its child controls based on whether a MCMS placeholder on the posting contained any content - for example, we have an article page that can optionally display an image, and the image can optionally have a caption appearing below it - if there's no image, then everything under it should naturally float up the page, and the same for no caption. As there's a certain amount of html and css needed to get this to work, it's nicer if this isn't rendered out if there's no image, hence this control. However, to get this to work properly, with nested conditional controls, we needed to do our magic in the AddParsedSubObject method which runs before the ScriptManager did it's stuff - and if you try and call the posting templated directly without a CMS context (all those NR querystrings) and then try and access the CMS objects it all goes belly up.

Thankfully, there's a simpe solution - the ToolkitScriptManager that is part of the ASP.Net AJAX Control Extensions has a very nice property on it: CombineScriptsHandlerUrl - by default the ToolkitScriptManager will attempt to combine all the required scripts into one nice big script, instead of lots of little ones - and this property tells it what page it should call to do this - I think technically you should use an .ashx page for this - although an .aspx page with the ToolkitScriptManager on it works just as well - there's some detailed information on the CombineScriptsHandlerUrl here. Setting this property to a non-CMS page has fixed all our problems.

Another nice feature in ASP.Net 3.5 is the ScriptManagerProxy - this allows you to place a ScriptManager (or ToolkitScriptManager) on your master page and then place the ScriptManagerProxy in the page or user control as required. This means that to register a client script block instead of writing something like:

var scriptManager = ToolkitScriptManager.GetCurrent(Page);
if (null != scriptManager)
  { scriptManager.RegisterClientScriptBlock([...]); }

You can just call the local ScriptManagerProxy with the same methods. All very nice.


Part of the Series: The joys of using .Net 2.0, .Net 3.0 and .Net 3.5 with MCMS

Filed under: ASP.NET

Developing MCMS projects with Visual Studio 2005/2008

Posted 07 July 2008, 11:27 | by | Perma-link

This is I guess a prequel to the first post in the series "The joys of using .Net 2.0, .Net 3.0 and .Net 3.5 with MCMS", as after posting that, I receieved the following question:

"As I read the entry you seem to use Visual Studio 2008 with an MCMS enabled site. I've not been able to get that to work. How exactly did you do that?"

Well, here finally, is the online version of my reply (the reply was more timely than this),

As you know, with Visual Studio (VS) 2003 you could work with MCMS in a nice connected way; if you installed MCMS on the same machine as VS you would have access to some extra projects: "MCMS Web Application", "MCMS Web Service" and "MCMS Empty Web Project". These projects would do a couple of things for you:

  1. Add the references to the MCMS libraies, and enable the MCMS placeholders in the toolbox
  2. Add a line to the project file indicating that this was an MCMS project

As an MCMS project, it was hooked up to the local instance of the MCMS database and the Template Manager window in VS was enabled alongside the other docked windows like Solution Explorer.

Generally, our development environment has been that our desktop machines run a standard workstation operating system (typically Windows XP), and then we run various virtual servers with server operating systems that replicate the client's live environment - as VS won't be installed there, it makes sense not to have it installed in development - less chance of the incorrect settings filtering through - so we run VS on the workstations.

As VS runs on a separate machine to MCMS, we found that we had issues with the integration aspects - opening projects would take a long time as connections to the database were sorted out, and as our workstations were upgraded, the chances of having MCMS installed locally minimised.

We therefore tend to work with a standard Web Site/Web Application project (we've stuck with Web Applications as it makes deloyments slightly easier - one dll instead of all those .cs files - but for the initial prototyping of my last project I was using a Web Site to make buildling/debugging faster - MCMS worked fine under both).

You can easily add the MCMS controls to the VS toolbox as you would any other controls for design time creation:

  1. Right click on the toolbox, "Add Tab" and give it a suitable name ("MCMS Controls" or something).
  2. Right click on the new tab, "Choose Items...", navigate to the MCMS DLLs ([MCMS Install Folder]\Server\Bin\) and open "Microsoft. ContentManagement. Publishing. Extensions. Placeholders.dll" and pick the controls you want from there.

You can then build your site as you would any other site, creating master pages, .aspx pages and .ascx controls with whatever placeholders you need.

We would then hook up these pages using Steve Walker's "Template Manager" to create and edit our template definitions - this basically wrapped the Template Manager window from VS in a standalone application - I wasn't able to find it online when I looked recently - it was on GotDotNet, and probably died with the site.

The only other thing to be aware of is that Site Manager requires the Visual J# redistributable to run - and that this hasn't been updated beyond .Net 2.0

The two senarios here are:

  1. Site Manager is already installed before you upgrade to .Net 2.0 +
    As the .Net 3.0/3/5 applications actually still run under the .Net 2.0 runtime, the simplist option is to download and install the Visual J# 2.0 Redistributable. If you don't want to do that, you can create a .config file next to the Site Manager executable ([MCMS Install Folder]\Client\NRClient.exe.config) with the following contents:
    <configuration>
      <startup>
        <supportedruntime version="v1.1.4322"></supportedruntime>
      </startup>
    </configuration>
  2. Site Manager is not installed, and you've already installed .Net 3.0+
    This is slightly trickier - the installer complains that you need the Visual J# .Net Redistributable package version 3.5 installed - which is impossible as there isn't a version beyond 2.0. Basically the installer is looking in the registry for the highest version number under:
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP
    If you rename the v3.0 and v3.5 keys, all you need is the version 2.0 redistributable installed, then you can install Site Manager, and finally rename the keys back

Happy coding.


Part of the Series: The joys of using .Net 2.0, .Net 3.0 and .Net 3.5 with MCMS

Filed under: ASP.NET

MCMS, Master Pages and Nested Master Pages

Posted 25 January 2008, 16:50 | by | Perma-link

Obviously, there's no problem with using Master Pages with MCMS SP2, so there's a big win right there, however now that Visual Studio 2008 supports Nested Master Pages we're on a real roll.

We've defined a number of shared placeholders (e.g. page title, browser title, keywords, meta description, etc) that all templates should have, and most of these are metadata fields – more on this in a minute.

So first of all we build a base master page with all the basic page layout – headers, left hand navigation, footers, standard CSS classes, MCMS Console, and then build up some additional master pages to layout the pages further – for example one with one main content column and one with two main content columns.

We can then build our pages on these child master pages, but still have all the layout of the base master defined in one place.

The MCMS placeholders can obviously exist quite happily on any of these controls (page, specific master page or base master page, or indeed on a user control).

You remember that I said a lot of the shared placeholders where metadata? And clearly when the author is editing the page they need to be able to see the controls for them, but in presentation mode these controls need to render out in the page's metadata elements. So how did we go about this? Simple: Create a class that inherits from System.Web.UI.Page, and change our pages to inherit from this “BasePage” instead. Then, we can override the OnPreInit event and do the following:

if (CurrentUserIsAuthoring()) {
  Page.Master.MasterPageFile = "/MasterPages/BaseAuthoring.Master";
}
base.OnPreInit(e);

So we pick up the page's Master, and then change the Master Page file of that to our Base Authoring master page instead of the Base Presentation master page. This also allows us to completely change the display and style of the page in Authoring mode as well – something the client is quite keen on.


Part of the Series: The joys of using .Net 2.0, .Net 3.0 and .Net 3.5 with MCMS

Filed under: ASP.NET

The joys of using .Net 2.0, .Net 3.0 and .Net 3.5 with MCMS

Posted 25 January 2008, 16:45 | by | Perma-link

Well, I suppose I’d best admit it – otherwise there’ll be no more blog posts for a while. The current project I’m working on is still in MCMS – an issue with the clients deadline and us get the team up to speed with MOSS really – however we are using the latest versions of the framework – so that means ASP.NET 2.0 on the server, and taking advantage of the features in .Net 3.5 where appropriate.

The main things we’re using so far are:

(Seeing as the first item on that list has been sitting on my hard drive for about two weeks now, I guess this is going to be an instalments piece - I’ll update the list with links as things are posted)

Filed under: ASP.NET

We're alive!

Posted 20 December 2006, 11:07 | by | Perma-link

Hooray! We've finally upgraded the last of our sites to ASP.Net 2.0, and all the goodness that entails - Generics, Membership Providers, Master Pages, the works.

I have to say, while the profile provider out of the box is a little rubbish - the entire profile is stored as a delimited field in one column of the database - the rest of it's wonderful. Only thing that confused me for a little while was the application names - if you change the name in the membership provider, don't forget to change it for all the other providers (roles, profiles, etc). No real point in logging-in/registering at the moment, I've not wired anything up to use it yet.

Apologies to those of you that already subscribed to this feed - I think you may have received the last ten items again yesterday, all lumped in as published yesterday (or whenever you updated the feed) - I'd forgotten to set the pubDate element on the items.

Quick notes of appreciation:

  • New hosts - LiquidSix.co.uk currently very efficient, affordable, etc.
  • I'm using the FCKEditor as the main interface for writing this (on top of my own little system for blogging).
  • I'll hold my hand up and admit that the MS Personal Web Site Starter Kit was ripped apart for the albums section.

Enjoy.

Filed under: ASP.NET

Getting AjaxPro to behave in the Global Assembly Cache (GAC)

Posted 08 November 2006, 23:52 | by | Perma-link

Getting AjaxPro (CodePlex Start Kit) to behave in the Global Assembly Cache (GAC). While the means to do this are available in the Starter Kit, the "Quick Guide" doesn't really cover the main points here.

If you need to deploy AjaxPro to a web server via the GAC here are the key points to remember:

Modify your web.config as follows (this is for the .Net 1.1 version, for the .Net 2.0, just change "AjaxPro" to "AjaxPro.2"):

In configSections block add the following:

<sectionGroup name="ajaxNet">
    <section name="ajaxSettings"
             type="AjaxPro.AjaxSettingsSectionHandler,
                   AjaxPro,
                   Version=6.10.6.2,
                   Culture=neutral,
                   PublicKeyToken=4735ae9824c7d3ec" />
</sectionGroup>

Remember to update the version number to the version you are using - Michael Schwarz is quite prolific with his releases.

Then add the following as a direct child of configuration:

<ajaxNet>
    <ajaxSettings>
        <urlNamespaceMappings useAssemblyQualifiedName="true" allowListOnly="false" />
        <debug enabled="false" />
    </ajaxSettings>
</ajaxNet>

The key element here is the useAssemblyQualifiedName="true" attribute. This tells the AjaxPro library to output the calls to your AjaxMethods as fully qualified paths - with the version number, culture and public key information.

Finally, add the following block as a child of the system.web element:

<httpHandlers>
    <add verb="GET,POST"
         path="AjaxPro/*.ashx"
         type="AjaxPro.AjaxHandlerFactory,
               AjaxPro,
               Version=6.10.6.2,
               Culture=neutral,
               PublicKeyToken=4735ae9824c7d3ec"/>
</httpHandlers>

Again, updating the version number as required.

You can then follow the rest of the quick start guide to set up your first AjaxPro page.

I'm going to write up a note on error reporting tomorrow hopefully, but the key point here is you'll notice that I left in the Debug element in the ajaxNet section, set the value to true and you will be able to retrieve the stack, trace and lines from the exception as well as just the message.

Filed under: ASP.NET