July 16th, 2008

hacking restful_authentication

I had a peculiar requirement in a recent project recently, in which we used restful_authentication, where we needed to protect every controller and action from access without authentication. However these same actions also responded to XML requests for ActiveResource models in another app. The XML needed to be available without authentication. This posed more difficulties than I thought it would.

Bypassing authentication for XML requests

I created a controller class to act as the parent class for all controllers that required authentication (the wisdom of the inheritance strategy here is yet to be established). It applies the login_required filter to all actions, thus protecting all inherited controllers.

To filter out XML requests I took my inspiration from the respond_to method in Rails. This uses a Responder, which in turn uses some sort of mime-type lookup of the format requested by the client. I used this approach in an overridden version of login_required.

# ...
  class Responder
    def initialize(controller)
      @controller = controller
      @request    = controller.request
      @response   = controller.response

      @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)

      @order     = []
      @responses = {}
    end
    # ...
  end
# ...

The one stumbling block I encountered was that I couldn’t get at the instance properties of Mime::Type in order to decide whether we needed authentication or not. This was because there were no accessors for those properties. In order to get access to them I extended the instance of Mime::Type with a new method to expose the symbol instance property.

class ApplicationAuthController < ApplicationController

  before_filter :login_required

  protected

  # Overidden login_required to allow XML requests through
  def login_required
    mime_type = Array(Mime::Type.lookup_by_extension(request.parameters[:format]) || request.accepts).first

    # We need to get access to the @symbol property of this object. However it has no accessor.
    # We extend this instance only to have a to_sym method
    class << mime_type
      def to_sym
        @symbol
      end
    end

    logger.debug "Request format: #{mime_type.to_sym}"

    super if mime_type.to_sym != :xml
  end
end

As you can see, XML requests bypass authentication altogether.

There are dangers in this approach, especially when the assumption of the presence of a valid current_user is made in actions that might deliver both normal application output as well as XML. Some suitably targeted specs should limit the scope for broken actions here.

Sorry, comments are closed for this article.