Skip to content

Commit

Permalink
Small but powerful change: Strategy options now inherit from a defaul…
Browse files Browse the repository at this point in the history
…t, are Mashes, and can be declaratively configured.
  • Loading branch information
Michael Bleigh committed Sep 26, 2011
1 parent 98bc2b9 commit 53bc3f5
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 4 deletions.
6 changes: 6 additions & 0 deletions lib/omniauth.rb
Expand Up @@ -74,6 +74,12 @@ def add_mock(provider, mock={})
self.mock_auth[provider.to_sym] = mock
end

# This is a convenience method to be used by strategy authors
# so that they can add special cases to the camelization utility
# method that allows OmniAuth::Builder to work.
#
# @param name [String] The underscored name, e.g. `oauth`
# @param camelized [String] The properly camelized name, e.g. 'OAuth'
def add_camelization(name, camelized)
self.camelizations[name.to_s] = camelized.to_s
end
Expand Down
73 changes: 69 additions & 4 deletions lib/omniauth/strategy.rb
@@ -1,4 +1,5 @@
require 'omniauth'
require 'hashie/mash'

module OmniAuth
class NoSessionError < StandardError; end
Expand All @@ -12,12 +13,58 @@ def self.included(base)
base.class_eval do
attr_reader :app, :name, :env, :options, :response
end
base.extend ClassMethods
end

module ClassMethods
# Returns an inherited set of default options set at the class-level
# for each strategy.
def default_options
return @default_options if @default_options
existing = superclass.respond_to?(:default_options) ? superclass.default_options : {}
@default_options = OmniAuth::Strategy::Options.new(existing)
end

# This allows for more declarative subclassing of strategies by allowing
# default options to be set using a simple configure call.
#
# @param options [Hash] If supplied, these will be the default options (deep-merged into the superclass's default options).
# @yield [Options] The options Mash that allows you to set your defaults as you'd like.
#
# @example Using a yield to configure the default options.
#
# class MyStrategy
# include OmniAuth::Strategy
#
# configure do |c|
# c.foo = 'bar'
# end
# end
#
# @example Using a hash to configure the default options.
#
# class MyStrategy
# include OmniAuth::Strategy
# configure foo: 'bar'
# end
def configure(options = nil)
yield default_options and return unless options
default_options.deep_merge!(options)
end
end

# Initializes the strategy by passing in the Rack endpoint,
# the unique URL segment name for this strategy, and any
# additional arguments. An `options` hash is automatically
# created from the last argument if it is a hash.
#
# @param app [Rack application] The application on which this middleware is applied.
# @param name [String] A unique URL segment to describe this particular strategy. For example, `'openid'`.
# @yield [Strategy] Yields itself for block-based configuration.
def initialize(app, name, *args, &block)
@app = app
@name = name.to_sym
@options = args.last.is_a?(Hash) ? args.pop : {}
@name = name.to_s
@options = self.class.default_options.deep_merge(args.last.is_a?(Hash) ? args.pop : {})

yield self if block_given?
end
Expand All @@ -26,10 +73,17 @@ def inspect
"#<#{self.class.to_s}>"
end

# Duplicates this instance and runs #call! on it.
# @param [Hash] The Rack environment.
def call(env)
dup.call!(env)
end

# The logic for dispatching any additional actions that need
# to be taken. For instance, calling the request phase if
# the request path is recognized.
#
# @param env [Hash] The Rack environment.
def call!(env)
raise OmniAuth::NoSessionError.new("You must provide a session to use OmniAuth.") unless env['rack.session']

Expand Down Expand Up @@ -75,6 +129,8 @@ def callback_call
callback_phase
end

# Returns true if the environment recognizes either the
# request or callback path.
def on_auth_path?
on_request_path? || on_callback_path?
end
Expand All @@ -95,6 +151,9 @@ def options_request?
request.request_method == 'OPTIONS'
end

# This is called in lieu of the normal request process
# in the event that OmniAuth has been configured to be
# in test mode.
def mock_call!(env)
return mock_request_call if on_request_path?
return mock_callback_call if on_callback_path?
Expand All @@ -115,7 +174,7 @@ def mock_request_call

def mock_callback_call
setup_phase
mocked_auth = OmniAuth.mock_auth_for(name.to_sym)
mocked_auth = OmniAuth.mock_auth_for(name.to_s)
if mocked_auth.is_a?(Symbol)
fail!(mocked_auth)
else
Expand All @@ -126,6 +185,10 @@ def mock_callback_call
end
end

# The setup phase looks for the `:setup` option to exist and,
# if it is, will call either the Rack endpoint supplied to the
# `:setup` option or it will call out to the setup path of the
# underlying application. This will default to `/auth/:provider/setup`.
def setup_phase
if options[:setup].respond_to?(:call)
options[:setup].call(env)
Expand Down Expand Up @@ -183,7 +246,7 @@ def call_app!(env = @env)
end

def auth_hash
AuthHash.new(:provider => name.to_s)
AuthHash.new(:provider => name)
end

def full_host
Expand Down Expand Up @@ -238,5 +301,7 @@ def fail!(message_key, exception = nil)

OmniAuth.config.on_failure.call(self.env)
end

class Options < Hashie::Mash; end
end
end
36 changes: 36 additions & 0 deletions spec/omniauth/strategy_spec.rb
Expand Up @@ -29,6 +29,37 @@ def make_env(path = '/auth/test', props = {})

describe OmniAuth::Strategy do
let(:app){ lambda{|env| [404, {}, ['Awesome']]}}

describe '.default_options' do
it 'should be inherited from a parent class' do
superklass = Class.new
superklass.send :include, OmniAuth::Strategy
superklass.configure do |c|
c.foo = 'bar'
end

klass = Class.new(superklass)
klass.default_options.foo.should == 'bar'
end
end

describe '.configure' do
subject { klass = Class.new; klass.send :include, OmniAuth::Strategy; klass }
it 'should take a block and allow for default options setting' do
subject.configure do |c|
c.wakka = 'doo'
end
subject.default_options.should == {"wakka" => "doo"}
end

it 'should take a hash and deep merge it' do
subject.configure :abc => {:def => 123}
subject.configure :abc => {:hgi => 456}
subject.default_options.should == {'abc' => {'def' => 123, 'hgi' => 456}}
end
end


describe '#initialize' do
context 'options extraction' do
it 'should be the last argument if the last argument is a Hash' do
Expand All @@ -38,6 +69,11 @@ def make_env(path = '/auth/test', props = {})
it 'should be a blank hash if none are provided' do
ExampleStrategy.new(app, 'test').options.should == {}
end

it 'should be the default options if any are provided' do
ExampleStrategy.stub!(:default_options).and_return(OmniAuth::Strategy::Options.new(:abc => 123))
ExampleStrategy.new(app, 'test').options.abc.should == 123
end
end
end

Expand Down
6 changes: 6 additions & 0 deletions spec/omniauth_spec.rb
Expand Up @@ -11,6 +11,12 @@
end

context 'configuration' do
describe '.defaults' do
it 'should be a hash of default configuration' do
OmniAuth::Configuration.defaults.should be_kind_of(Hash)
end
end

it 'should be callable from .configure' do
OmniAuth.configure do |c|
c.should be_kind_of(OmniAuth::Configuration)
Expand Down

0 comments on commit 53bc3f5

Please sign in to comment.