warden 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ module Warden
2
+ # The middleware for Rack Authentication
3
+ # The middlware requires that there is a session upstream
4
+ # The middleware injects an authentication object into
5
+ # the rack environment hash
6
+ class Manager
7
+ attr_accessor :config, :failure_app
8
+
9
+ # initialize the middleware.
10
+ # Provide a :failure_app in the options to setup an application to run when there is a failure
11
+ # The manager is yielded when initialized with a block. This is useful when declaring it in Rack::Builder
12
+ # :api: public
13
+ def initialize(app, config = {})
14
+ @app = app
15
+ @config = config
16
+ yield self if block_given?
17
+
18
+ # should ensure there is a failure application defined.
19
+ @failure_app = config[:failure_app] if config[:failure_app]
20
+ raise "No Failure App provided" unless @failure_app
21
+ self
22
+ end
23
+
24
+ # Set the default strategies to use.
25
+ # :api: public
26
+ def default_strategies(*strategies)
27
+ if strategies.empty?
28
+ @config[:default_strategies]
29
+ else
30
+ @config[:default_strategies] = strategies.flatten
31
+ end
32
+ end
33
+
34
+ # :api: private
35
+ def call(env) # :nodoc:
36
+ # if this is downstream from another warden instance, don't do anything.
37
+ return @app.call(env) unless env['warden'].nil?
38
+
39
+ env['warden'] = Proxy.new(env, @config)
40
+ result = catch(:warden) do
41
+ @app.call(env)
42
+ end
43
+
44
+ result ||= {}
45
+ case result
46
+ when Array
47
+ if result.first != 401
48
+ return result
49
+ else
50
+ call_failure_app(env)
51
+ end
52
+ when Hash
53
+ if (result[:action] ||= :unauthenticated) == :unauthenticated
54
+ process_unauthenticated(result,env)
55
+ end # case result
56
+ end
57
+ end
58
+
59
+ class << self
60
+
61
+
62
+ # Does the work of storing the user in the session
63
+ # :api: private
64
+ def _store_user(user, session, scope = :default) # :nodoc:
65
+ return nil if user.nil?
66
+ session["warden.user.#{scope}.key"] = serialize_into_session.call(user)
67
+ end
68
+
69
+ # Does the work of fetching the user from the session
70
+ # :api: private
71
+ def _fetch_user(session, scope = :default) # :nodoc:
72
+ key = session["warden.user.#{scope}.key"]
73
+ return nil if key.nil?
74
+ serialize_from_session.call(key)
75
+ end
76
+
77
+ # Prepares the user to serialize into the session.
78
+ # Any object that can be serialized into the session in some way can be used as a "user" object
79
+ # Generally however complex object should not be stored in the session.
80
+ # If possible store only a "key" of the user object that will allow you to reconstitute it.
81
+ #
82
+ # Example:
83
+ # Warden::Manager.serialize_into_session{ |user| user.id }
84
+ #
85
+ # :api: public
86
+ def serialize_into_session(&block)
87
+ @serialize_into_session = block if block_given?
88
+ @serialize_into_session ||= lambda{|user| user}
89
+ end
90
+
91
+ # Reconstitues the user from the session.
92
+ # Use the results of user_session_key to reconstitue the user from the session on requests after the initial login
93
+ #
94
+ # Example:
95
+ # Warden::Manager.serialize_from_session{ |id| User.get(id) }
96
+ #
97
+ # :api: public
98
+ def serialize_from_session(&blk)
99
+ @serialize_from_session = blk if block_given?
100
+ @serialize_from_session ||= lambda{|key| key}
101
+ end
102
+ end
103
+
104
+ private
105
+ # When a request is unauthentiated, here's where the processing occurs.
106
+ # It looks at the result of the proxy to see if it's been executed and what action to take.
107
+ # :api: private
108
+ def process_unauthenticated(result, env)
109
+ case env['warden'].result
110
+ when :failure
111
+ call_failure_app(env, result)
112
+ when :redirect
113
+ [env['warden']._status, env['warden'].headers, [env['warden'].message || "You are being redirected to #{env['warden'].headers['Location']}"]]
114
+ when :custom
115
+ env['warden'].custom_response
116
+ when nil
117
+ call_failure_app(env, result)
118
+ end # case env['warden'].result
119
+ end
120
+
121
+ # Calls the failure app.
122
+ # The before_failure hooks are run on each failure
123
+ # :api: private
124
+ def call_failure_app(env, opts = {})
125
+ env["PATH_INFO"] = "/#{opts[:action]}"
126
+ env["warden.options"] = opts
127
+
128
+ # Call the before failure callbacks
129
+ Warden::Manager._before_failure.each{|hook| hook.call(env,opts)}
130
+
131
+ @failure_app.call(env).to_a
132
+ end # call_failure_app
133
+ end
134
+ end # Warden
@@ -0,0 +1,25 @@
1
+ module Warden
2
+ module Mixins
3
+ module Common
4
+
5
+ # Convinience method to access the session
6
+ # :api: public
7
+ def session
8
+ @env['rack.session']
9
+ end # session
10
+
11
+ # Convenience method to access the rack request
12
+ # :api: public
13
+ def request
14
+ @request ||= Rack::Request.new(@env)
15
+ end # request
16
+
17
+ # Convenience method to access the rack request params
18
+ # :api: public
19
+ def params
20
+ request.params
21
+ end # params
22
+
23
+ end # Common
24
+ end # Mixins
25
+ end # Warden
@@ -0,0 +1,200 @@
1
+ module Warden
2
+ class UserNotSet < RuntimeError; end
3
+
4
+ class Proxy
5
+ # :api: private
6
+ attr_accessor :winning_strategy
7
+
8
+ # An accessor to the rack env hash
9
+ # :api: public
10
+ attr_reader :env
11
+
12
+ extend ::Forwardable
13
+ include ::Warden::Mixins::Common
14
+ alias_method :_session, :session
15
+
16
+ # :api: private
17
+ def_delegators :winning_strategy, :headers, :message, :_status, :custom_response
18
+
19
+ def initialize(env, config = {}) # :nodoc:
20
+ @env = env
21
+ @config = config
22
+ @strategies = @config.fetch(:default_strategies, [])
23
+ @users = {}
24
+ errors # setup the error object in the session
25
+ end
26
+
27
+ # Check to see if there is an authenticated user for the given scope.
28
+ # When scope is not specified, :default is assumed.
29
+ #
30
+ # Parameters:
31
+ # args - a list of symbols (labels) that name the strategies to attempt
32
+ # opts - an options hash that contains the :scope of the user to check
33
+ #
34
+ # Example:
35
+ # env['warden'].authenticated?(:password, :scope => :admin)
36
+ # :api: public
37
+ def authenticated?(*args)
38
+ scope = scope_from_args(args)
39
+ _perform_authentication(*args)
40
+ !user(scope).nil?
41
+ end # authenticated?
42
+
43
+ # Run the authentiation strategies for the given strategies.
44
+ # If there is already a user logged in for a given scope, the strategies are not run
45
+ # This does not halt the flow of control and is a passive attempt to authenticate only
46
+ # When scope is not specified, :default is assumed.
47
+ #
48
+ # Parameters:
49
+ # args - a list of symbols (labels) that name the strategies to attempt
50
+ # opts - an options hash that contains the :scope of the user to check
51
+ #
52
+ # Example:
53
+ # env['auth'].authenticate(:password, :basic, :scope => :sudo)
54
+ # :api: public
55
+ def authenticate(*args)
56
+ scope = scope_from_args(args)
57
+ _perform_authentication(*args)
58
+ user(scope)
59
+ end
60
+
61
+ # The same as +authenticate+ except on failure it will throw an :warden symbol causing the request to be halted
62
+ # and rendered through the +failure_app+
63
+ #
64
+ # Example
65
+ # env['warden'].authenticate!(:password, :scope => :publisher) # throws if it cannot authenticate
66
+ #
67
+ # :api: public
68
+ def authenticate!(*args)
69
+ opts = opts_from_args(args)
70
+ scope = scope_from_args(args)
71
+ _perform_authentication(*args)
72
+ throw(:warden, opts.merge(:action => :unauthenticated)) if !user(scope)
73
+ user(scope)
74
+ end
75
+
76
+ # Manually set the user into the session and auth proxy
77
+ #
78
+ # Parameters:
79
+ # user - An object that has been setup to serialize into and out of the session.
80
+ # opts - An options hash. Use the :scope option to set the scope of the user
81
+ # :api: public
82
+ def set_user(user, opts = {})
83
+ scope = (opts[:scope] ||= :default)
84
+ Warden::Manager._store_user(user, _session, scope) # Get the user into the session
85
+
86
+ # Run the after hooks for setting the user
87
+ Warden::Manager._after_set_user.each{|hook| hook.call(user, self, opts)}
88
+
89
+ @users[scope] = user # Store the user in the proxy user object
90
+ end
91
+
92
+ # Provides acccess to the user object in a given scope for a request.
93
+ # will be nil if not logged in
94
+ #
95
+ # Example:
96
+ # # without scope (default user)
97
+ # env['warden'].user
98
+ #
99
+ # # with scope
100
+ # env['warden'].user(:admin)
101
+ #
102
+ # :api: public
103
+ def user(scope = :default)
104
+ @users[scope]
105
+ end
106
+
107
+ # Provides a scoped session data for authenticated users.
108
+ # Warden manages clearing out this data when a user logs out
109
+ #
110
+ # Example
111
+ # # default scope
112
+ # env['warden'].data[:foo] = "bar"
113
+ #
114
+ # # :sudo scope
115
+ # env['warden'].data(:sudo)[:foo] = "bar"
116
+ #
117
+ # :api: public
118
+ def session(scope = :default)
119
+ raise NotAuthenticated, "#{scope.inspect} user is not logged in" unless authenticated?(:scope => scope)
120
+ _session["warden.user.#{scope}.session"] ||= {}
121
+ end
122
+
123
+ # Provides logout functionality.
124
+ # The logout also manages any authenticated data storage and clears it when a user logs out.
125
+ #
126
+ # Parameters:
127
+ # scopes - a list of scopes to logout
128
+ #
129
+ # Example:
130
+ # # Logout everyone and clear the session
131
+ # env['warden'].logout
132
+ #
133
+ # # Logout the default user but leave the rest of the session alone
134
+ # env['warden'].logout(:default)
135
+ #
136
+ # # Logout the :publisher and :admin user
137
+ # env['warden'].logout(:publisher, :admin)
138
+ #
139
+ # :api: public
140
+ def logout(*scopes)
141
+ if scopes.empty?
142
+ _session.clear
143
+ else
144
+ scopes.each do |s|
145
+ _session["warden.user.#{s}.key"] = nil
146
+ _session["warden.user.#{s}.session"] = nil
147
+ end
148
+ end
149
+ end
150
+
151
+ # proxy methods through to the winning strategy
152
+ # :api: private
153
+ def result # :nodoc:
154
+ winning_strategy.nil? ? nil : winning_strategy.result
155
+ end
156
+
157
+ private
158
+ # :api: private
159
+ def _perform_authentication(*args)
160
+ scope = scope_from_args(args)
161
+ opts = opts_from_args(args)
162
+ # Look for an existing user in the session for this scope
163
+ if @users[scope] || set_user(Warden::Manager._fetch_user(_session, scope), :scope => scope)
164
+ return @users[scope]
165
+ end
166
+
167
+ # If there was no user in the session. See if we can get one from the request
168
+ strategies = args.empty? ? @strategies : args
169
+ raise "No Strategies Found" if strategies.empty? || !(strategies - Warden::Strategies._strategies.keys).empty?
170
+ strategies.each do |s|
171
+ strategy = Warden::Strategies[s].new(@env, @conf)
172
+ self.winning_strategy = strategy
173
+ next unless strategy.valid?
174
+ strategy._run!
175
+ break if strategy.halted?
176
+ end
177
+
178
+
179
+ if winning_strategy && winning_strategy.user
180
+ set_user(winning_strategy.user, opts)
181
+
182
+ # Run the after_authentication hooks
183
+ Warden::Manager._after_authentication.each{|hook| hook.call(winning_strategy.user, self, opts)}
184
+ end
185
+
186
+ winning_strategy
187
+ end
188
+
189
+ # :api: private
190
+ def scope_from_args(args)
191
+ Hash === args.last ? args.last.fetch(:scope, :default) : :default
192
+ end
193
+
194
+ # :api: private
195
+ def opts_from_args(args)
196
+ Hash === args.last ? args.pop : {}
197
+ end
198
+
199
+ end # Proxy
200
+ end # Warden
@@ -0,0 +1,51 @@
1
+ module Warden::Spec
2
+ module Helpers
3
+
4
+ FAILURE_APP = lambda{|e|[401, {"Content-Type" => "text/plain"}, ["You Fail!"]] }
5
+
6
+ def env_with_params(path = "/", params = {})
7
+ method = params.fetch(:method, "GET")
8
+ Rack::MockRequest.env_for(path, :input => Rack::Utils.build_query(params),
9
+ 'HTTP_VERSION' => '1.1',
10
+ 'REQUEST_METHOD' => "#{method}")
11
+ end
12
+
13
+ def setup_rack(app = nil, opts = {}, &block)
14
+ app ||= block if block_given?
15
+ # opts[:default_strategies] ||= [:password]
16
+ # opts[:failure_app] ||= failure_app
17
+ Rack::Builder.new do
18
+ use Warden::Spec::Helpers::Session
19
+ use Warden::Manager, opts do |manager|
20
+ manager.failure_app = Warden::Spec::Helpers::FAILURE_APP
21
+ manager.default_strategies :password
22
+ end
23
+ run app
24
+ end
25
+ end
26
+
27
+ def valid_response
28
+ [200,{'Content-Type' => 'text/plain'},'OK']
29
+ end
30
+
31
+ def failure_app
32
+ Warden::Spec::Helpers::FAILURE_APP
33
+ end
34
+
35
+ def success_app
36
+ lambda{|e| [200, {"Content-Type" => "text/plain"}, ["You Win"]]}
37
+ end
38
+
39
+ class Session
40
+ attr_accessor :app
41
+ def initialize(app,configs = {})
42
+ @app = app
43
+ end
44
+
45
+ def call(e)
46
+ e['rack.session'] ||= {}
47
+ @app.call(e)
48
+ end
49
+ end # session
50
+ end
51
+ end
@@ -0,0 +1,16 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'rubygems'
4
+ require 'rack'
5
+ require 'warden'
6
+
7
+ Dir[File.join(File.dirname(__FILE__), "warden", "strategies", "**/*.rb")].each do |f|
8
+ require f
9
+ end
10
+ Dir[File.join(File.dirname(__FILE__), "helpers", "**/*.rb")].each do |f|
11
+ require f
12
+ end
13
+
14
+ Spec::Runner.configure do |config|
15
+ config.include(Warden::Spec::Helpers)
16
+ end
@@ -0,0 +1,111 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "authenticated data store" do
4
+
5
+ before(:each) do
6
+ @env = env_with_params
7
+ @env['rack.session'] = {
8
+ "warden.user.foo.key" => "foo user",
9
+ "warden.user.default.key" => "default user",
10
+ :foo => "bar"
11
+ }
12
+ end
13
+
14
+ it "should store data for the default scope" do
15
+ app = lambda do |e|
16
+ e['warden'].should be_authenticated(:pass)
17
+ e['warden'].should be_authenticated(:pass, :scope => :foo)
18
+
19
+ # Store the data for :deafult
20
+ e['warden'].session[:key] = "value"
21
+ valid_response
22
+ end
23
+ setup_rack(app).call(@env)
24
+ @env['rack.session']['warden.user.default.session'].should == {:key => "value"}
25
+ @env['rack.session']['warden.user.foo.session'].should be_nil
26
+ end
27
+
28
+ it "should store data for the foo user" do
29
+ app = lambda do |e|
30
+ e['warden'].session(:foo)[:key] = "value"
31
+ valid_response
32
+ end
33
+ setup_rack(app).call(@env)
34
+ @env['rack.session']['warden.user.foo.session'].should == {:key => "value"}
35
+ end
36
+
37
+ it "should store the data seperately" do
38
+ app = lambda do |e|
39
+ e['warden'].session[:key] = "value"
40
+ e['warden'].session(:foo)[:key] = "another value"
41
+ valid_response
42
+ end
43
+ setup_rack(app).call(@env)
44
+ @env['rack.session']['warden.user.default.session'].should == {:key => "value"}
45
+ @env['rack.session']['warden.user.foo.session' ].should == {:key => "another value"}
46
+ end
47
+
48
+ it "should clear the foo scoped data when foo logs out" do
49
+ app = lambda do |e|
50
+ e['warden'].session[:key] = "value"
51
+ e['warden'].session(:foo)[:key] = "another value"
52
+ e['warden'].logout(:foo)
53
+ valid_response
54
+ end
55
+ setup_rack(app).call(@env)
56
+ @env['rack.session']['warden.user.default.session'].should == {:key => "value"}
57
+ @env['rack.session']['warden.user.foo.session' ].should be_nil
58
+ end
59
+
60
+ it "should clear out the default data when :default logs out" do
61
+ app = lambda do |e|
62
+ e['warden'].session[:key] = "value"
63
+ e['warden'].session(:foo)[:key] = "another value"
64
+ e['warden'].logout(:default)
65
+ valid_response
66
+ end
67
+ setup_rack(app).call(@env)
68
+ @env['rack.session']['warden.user.default.session'].should be_nil
69
+ @env['rack.session']['warden.user.foo.session' ].should == {:key => "another value"}
70
+ end
71
+
72
+ it "should clear out all data when a general logout is performed" do
73
+ app = lambda do |e|
74
+ e['warden'].session[:key] = "value"
75
+ e['warden'].session(:foo)[:key] = "another value"
76
+ e['warden'].logout
77
+ valid_response
78
+ end
79
+ setup_rack(app).call(@env)
80
+ @env['rack.session']['warden.user.default.session'].should be_nil
81
+ @env['rack.session']['warden.user.foo.session' ].should be_nil
82
+ end
83
+
84
+ it "should logout multuiple personas at once" do
85
+ @env['rack.session']['warden.user.bar.key'] = "bar user"
86
+
87
+ app = lambda do |e|
88
+ e['warden'].session[:key] = "value"
89
+ e['warden'].session(:foo)[:key] = "another value"
90
+ e['warden'].session(:bar)[:key] = "yet another"
91
+ e['warden'].logout(:bar, :default)
92
+ valid_response
93
+ end
94
+ setup_rack(app).call(@env)
95
+ @env['rack.session']['warden.user.default.session'].should be_nil
96
+ @env['rack.session']['warden.user.foo.session' ].should == {:key => "another value"}
97
+ @env['rack.session']['warden.user.bar.session' ].should be_nil
98
+ end
99
+
100
+ it "should not store data for a user who is not logged in" do
101
+ @env['rack.session']
102
+ app = lambda do |e|
103
+ e['warden'].session(:not_here)[:key] = "value"
104
+ valid_response
105
+ end
106
+
107
+ lambda do
108
+ setup_rack(app).call(@env)
109
+ end.should raise_error(Warden::NotAuthenticated)
110
+ end
111
+ end