Negative Security Testing

Part of my day job is to help with software security at my company. Part of this involves reviewing our controllers to make sure that people who shouldn’t be able to access them can’t get in. I’ve created both Cucumber specs and RSpec examples to do this. So far I prefer the RSpec method since it allows me to do a little more introspection.

A little bit of a background on our setup. Like most Rails shops, we use CanCan for our authorization and Devise for the authentication. With CanCan we define roles for the users such that the roles are granted access to certain areas of the system.

When I first wrote up the blacklist test in Cucumber, I had to manually specify which roles weren’t allowed and which actions to test in each controller. Obviously, this would only do real testing up until a new role or action was added. When I switched to RSpec I was able to just give a whitelist of what roles are allowed and then get the available actions through introspection. See my code below:

class SecurityHelper
  def self.action_names(controller, opts = {})
    actions = controller.class.public_instance_methods(false).reject{|a| a.to_s.starts_with? '_'}
    actions - Array(opts[:except])
  end
 
  def self.create_insecure_user(opts = {})
    roles = User.available_roles - Array(opts[:allowed_roles]).map(&:to_s)
    FactoryGirl.create :user, roles: roles
  end
end
 
shared_examples "a secure controller" do |opts = {}|
  let(:actions) { SecurityHelper.action_names(controller, opts) }
  let(:insecure_user) { SecurityHelper.create_insecure_user(opts) }
 
  context "without authenticated user" do
    it "doesn't allow access without signing in" do
      sign_out :user
      actions.each do |action|
        get action.to_sym, id: ''
        response.should redirect_to new_user_session_url
      end
    end
 
    it "denies access to non-allowed roles" do
      actions.each do |action|
        sign_in insecure_user
        get action.to_sym, id: ''
        response.should redirect_to '/'
      end
    end
  end
end
 
 
#############################
## SUPPORT CONTROLLER HELP
#############################
 
shared_examples "a secure support controller" do |controller, opts = {}|
  include_examples "a secure controller", {allowed_roles: [:support]}.merge(opts)
end

I included an example of how to set it up for what I call the support controller.  All you need to do is give it the roles that are allowed and then it creates a user with every role except those.  Then in your controller spec, add this line:

it_behaves_like "a secure support controller"

Voilà! Now you know if anyone ever breaks the security in your controller.

As for the positive side of making sure that a good user can still get in, I just rely on the regular specs since any good test suite will hit every action at least once.

Comments are closed.