Skip to content

Commit

Permalink
Added global authentication and authorization callbacks (defined on A…
Browse files Browse the repository at this point in the history
…bstractResource)
  • Loading branch information
drogus committed May 2, 2011
1 parent 17f6ae8 commit 6706621
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 22 deletions.
8 changes: 4 additions & 4 deletions app/controllers/bulk/api_controller.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
class Bulk::ApiController < ActionController::Base
def get
options = { :json => Bulk::Resource.get(session, params) }
options = Bulk::Resource.get(self)
yield options if block_given?
render options
end

def create
options = { :json => Bulk::Resource.create(session, params) }
options = Bulk::Resource.create(self)
yield options if block_given?
render options
end

def update
options = { :json => Bulk::Resource.update(session, params) }
options = Bulk::Resource.update(self)
yield options if block_given?
render options
end

def delete
options = { :json => Bulk::Resource.delete(session, params) }
options = Bulk::Resource.delete(self)
yield options if block_given?
render options
end
Expand Down
56 changes: 43 additions & 13 deletions lib/bulk/resource.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
module Bulk
class AuthenticationError < StandardError; end
class AuthorizationError < StandardError; end

class Resource
module AbstractResourceMixin
def inherited(base)
Expand All @@ -8,11 +11,14 @@ def inherited(base)
end
end

attr_reader :session
attr_reader :controller
delegate :session, :params, :to => :controller

class << self
attr_accessor :resource_name, :resources
attr_accessor :abstract_resource_class
attr_reader :abstract
alias_method :abstract?, :abstract

def inherited(base)
if abstract_resource_class
Expand All @@ -25,48 +31,70 @@ def inherited(base)
end

self.abstract_resource_class = base
base.abstract!
base.extend AbstractResourceMixin
end

%w/get create update delete/.each do |method|
define_method(method) do |session, params|
handle_response(method, session, params)
define_method(method) do |controller|
handle_response(method, controller)
end
end

def abstract!
@abstract = true
end
protected :abstract!

private

# TODO: should it belong here or maybe I should move it to Bulk::Engine or some other class?
def handle_response(method, session, params)
# TODO: refactor this to some kind of Response class
def handle_response(method, controller)
response = {}
params.each do |resource, hash|
abstract_resource = abstract_resource_class.new(controller)

if abstract_resource.respond_to?(:authenticate)
raise AuthenticationError unless abstract_resource.authenticate
end

if abstract_resource.respond_to?(:authorize)
raise AuthorizationError unless abstract_resource.authorize
end

controller.params.each do |resource, hash|
next unless resources.nil? || resources.include?(resource.to_sym)
resource_object = instantiate(session, resource)
resource_object = instantiate_resource_class(controller, resource)
next unless resource_object
collection = resource_object.send(method, hash)
response.deep_merge! collection.to_hash(resource_object.plural_resource_name)
end
response

{ :json => response }
rescue AuthenticationError
{ :status => 401 }
rescue AuthorizationError
{ :status => 403 }
end

def instantiate(session, resource)
def instantiate_resource_class(controller, resource)
begin
"#{resource.to_s.pluralize}_resource".classify.constantize.new(session)
"#{resource.to_s.pluralize}_resource".classify.constantize.new(controller)
rescue NameError
begin
new(session, :resource_name => resource)
new(controller, :resource_name => resource)
rescue NameError
end
end
end
end

def initialize(session, options = {})
@session = session
def initialize(controller, options = {})
@controller = controller
@resource_name = options[:resource_name].to_s if options[:resource_name]

# try to get klass
klass
klass unless abstract?
end

def get(ids = 'all')
Expand Down Expand Up @@ -122,6 +150,8 @@ def resource_name
end

private
delegate :abstract?, :to => "self.class"

def set_with_validity_check(collection, id, record)
collection.set(id, record)
unless record.errors.empty?
Expand Down
71 changes: 66 additions & 5 deletions spec/bulk/resource_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,66 @@
end.should raise_error("Only one class can inherit from Bulk::Resource, your other resources should inherit from that class (currently it's: AbstractResource)")
end

context "global authentication" do
it "should run authentication callback before handling request" do
abstract_resource = Class.new do
cattr_accessor :authenticated
self.authenticated = false

define_method(:authenticate) do
self.class.authenticated = true
end
end
Bulk::Resource.abstract_resource_class = abstract_resource

controller = mock("controlelr", :params => {})
result = Bulk::Resource.get(controller)
abstract_resource.authenticated.should == true
result[:status].should be_nil
end

it "should set 401 status if authentication fails" do
abstract_resource = Class.new do
define_method(:authenticate) { false }
end
Bulk::Resource.abstract_resource_class = abstract_resource

controller = mock("controlelr", :params => {})
result = Bulk::Resource.get(controller)
result[:status].should == 401
end
end

context "global authorization" do
it "should run authorization callback before handling request" do
abstract_resource = Class.new do
cattr_accessor :authorized
self.authorized = false

define_method(:authorize) do
self.class.authorized = true
end
end
Bulk::Resource.abstract_resource_class = abstract_resource

controller = mock("controlelr", :params => {})
result = Bulk::Resource.get(controller)
abstract_resource.authorized.should == true
result[:status].should be_nil
end

it "should set 403 status if authentication fails" do
abstract_resource = Class.new do
define_method(:authorize) { false }
end
Bulk::Resource.abstract_resource_class = abstract_resource

controller = mock("controlelr", :params => {})
result = Bulk::Resource.get(controller)
result[:status].should == 403
end
end

shared_examples_for "Bulk::Resource subclass" do
context "#get" do
before do
Expand Down Expand Up @@ -147,7 +207,8 @@ def cant_delete

it "should skip resources that can't be resolved into classes" do
lambda {
Bulk::Resource.get(nil, :tasks => [1], :todos => [2])
controller = mock("controller", :params => { :tasks => [1], :todos => [2] })
Bulk::Resource.get(controller)
}.should_not raise_error
end
end
Expand All @@ -157,17 +218,17 @@ class TasksResource < AbstractResource
end

before do
session = ActionDispatch::Integration::Session.new(Rails.application)
@resource = TasksResource.new(session)
controller = mock("controller", :params => {})
@resource = TasksResource.new(controller)
end

it_behaves_like "Bulk::Resource subclass"
end

describe "not subclassed instance with resource name passed" do
before do
session = ActionDispatch::Integration::Session.new(Rails.application)
@resource = Bulk::Resource.new(session, :resource_name => :task)
controller = mock("controller", :params => {})
@resource = Bulk::Resource.new(controller, :resource_name => :task)
end

it_behaves_like "Bulk::Resource subclass"
Expand Down

0 comments on commit 6706621

Please sign in to comment.