hassox-warden 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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