Sorry this page looks weird. It was automatically migrated from my old blog, which had a different layout and different CSS.

Thinking Sphinx and (Declarative) Authorisation

Thinking Sphinx makes it wonderfully easy to search across multiple models:

ThinkingSphinx::Search.search 'Your query'

But what happens if your user is only allowed to see some of the data? On my current project, a signed-in user may see everything but a public user may only see part of the site. I need to restrict the search to what the user is allowed to see.

Fortunately what the user is allowed to see, in this case, depends on the class. For example a public user can see all news stories but no members' profiles. It would be harder were a public user allowed to see only some news stories or only some members' profiles.

First Solution

My first solution was to restrict the classes which Thinking Sphinx searches, using the handy :classes option:

class SearchController < ApplicationController

  def search
    if params[:q].blank?
      @results = []
    else
      @results = ThinkingSphinx::Search.search \\
        params[:q],
        :page    => params[:page] || 1,
        :classes => authorised_classes
    end
  end

  private

  def authorised_classes
    [Forum, Member, Post, Review, Story, Team, Topic].select do |klass|
      permitted_to? :read, klass.to_s.tableize.to_sym
    end
  end

end

The array of classes holds all the classes with indexes. (In reality my app has twice as many but I omitted some for legibility.)

The permitted_to? method comes from Steffen Bartsch’s excellent declarative_authorization plugin. There are many different ways to handle authorisation but this is my favourite. It has elegantly solved every authorisation requirement I have ever had; it just feels right.

As an aside, the authorisation rules are declared in config/authorization_rules.rb:

authorization do
  role :guest do
    has_permission_on :forums,  :to => :read
    has_permission_on :posts,   :to => :read
    # etc
  end

  role :member do
    has_permission_on :forums,  :to => :read
    has_permission_on :members, :to => :read
    has_permission_on :posts,   :to => :read
    # etc
  end
end

privileges do
  privilege :manage, :includes => [:create, :read, :update, :delete]
  privilege :read,   :includes => [:index, :show, :search]
  privilege :create, :includes => :new
  privilege :update, :includes => :edit
  privilege :delete, :includes => :destroy
end

You can see that a guest may search forums and posts, but not members; a member can search all of them.

Second Solution

My search controller’s authorised_classes method worked perfectly, but I knew I was setting myself up for a fall in a few months' time: as and when I added a new class to the domain, I would probably forget to add it to this array of indexed classes.

Of course Thinking Sphinx knows which of my classes have indexes, so we can leave it to Thinking Sphinx:

def authorised_classes
  ThinkingSphinx::indexed_models.select do |model|
    permitted_to? :read, model.tableize.to_sym
  end.map { |model| model.classify.constantize }
end

I think this is a pretty elegant way to integrate search and authorisation. Thanks to Pat Allan and Steffen Bartsch for writing high quality library code that makes my application code much easier to produce!

Comments

Thanks mate, just what I was looking for!

Alex Djioev • 16 March 2010

Andrew Stewart • 10 April 2009 • RailsDatabases
You can reach me by email or on Twitter.