merb-auth-core 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ module Merb
2
+ class Authentication
3
+
4
+ def errors
5
+ @errors ||= Errors.new
6
+ end
7
+
8
+ # Lifted from DataMapper's dm-validations plugin :)
9
+ # @author Guy van den Berg
10
+ # @since DM 0.9
11
+ class Errors
12
+
13
+ include Enumerable
14
+
15
+ # Clear existing authentication errors.
16
+ def clear!
17
+ errors.clear
18
+ end
19
+
20
+ # Add a authentication error. Use the field_name :general if the errors does
21
+ # not apply to a specific field of the Resource.
22
+ #
23
+ # @param <Symbol> field_name the name of the field that caused the error
24
+ # @param <String> message the message to add
25
+ def add(field_name, message)
26
+ (errors[field_name] ||= []) << message
27
+ end
28
+
29
+ # Collect all errors into a single list.
30
+ def full_messages
31
+ errors.inject([]) do |list,pair|
32
+ list += pair.last
33
+ end
34
+ end
35
+
36
+ # Return authentication errors for a particular field_name.
37
+ #
38
+ # @param <Symbol> field_name the name of the field you want an error for
39
+ def on(field_name)
40
+ errors_for_field = errors[field_name]
41
+ errors_for_field.blank? ? nil : errors_for_field
42
+ end
43
+
44
+ def each
45
+ errors.map.each do |k,v|
46
+ next if v.blank?
47
+ yield(v)
48
+ end
49
+ end
50
+
51
+ def empty?
52
+ entries.empty?
53
+ end
54
+
55
+ def method_missing(meth, *args, &block)
56
+ errors.send(meth, *args, &block)
57
+ end
58
+
59
+ private
60
+ def errors
61
+ @errors ||= {}
62
+ end
63
+
64
+ end # class Errors
65
+ end # Authentication
66
+ end # Merb
@@ -0,0 +1,6 @@
1
+ namespace :"merb-auth_core" do
2
+ # desc "Do something for merb-auth-core"
3
+ # task :default do
4
+ # puts "merb-auth-core doesn't do anything"
5
+ # end
6
+ end
@@ -0,0 +1,36 @@
1
+ module Merb
2
+ # These are not intended to be used directly
3
+ class Authentication
4
+ attr_accessor :body
5
+
6
+ def redirected?
7
+ !!headers["Location"]
8
+ end
9
+
10
+ def headers
11
+ @headers ||= {}
12
+ end
13
+
14
+ def status
15
+ @status ||= 200
16
+ end
17
+
18
+ def status=(sts)
19
+ @status = sts
20
+ end
21
+
22
+ def halted?
23
+ !!@halt
24
+ end
25
+
26
+ def headers=(headers)
27
+ raise ArgumentError, "Need to supply a hash to headers. Got #{headers.class}" unless headers.kind_of?(Hash)
28
+ @headers = headers
29
+ end
30
+
31
+ def halt!
32
+ @halt = true
33
+ end
34
+
35
+ end # Merb::Authentication
36
+ end # Merb
@@ -0,0 +1,25 @@
1
+ Merb::Router.extensions do
2
+
3
+ def authenticate(*strategies, &block)
4
+ p = Proc.new do |request, params|
5
+ if request.session.authenticated?
6
+ params
7
+ else
8
+ if strategies.blank?
9
+ result = request.session.authenticate!(request, params)
10
+ else
11
+ result = request.session.authenticate!(request, params, *strategies)
12
+ end
13
+
14
+ if request.session.authentication.halted?
15
+ auth = request.session.authentication
16
+ [auth.status, auth.headers, auth.body]
17
+ else
18
+ params
19
+ end
20
+ end
21
+ end
22
+ defer(p, &block)
23
+ end
24
+
25
+ end
@@ -0,0 +1,57 @@
1
+ module Merb
2
+ module Session
3
+
4
+ def self.included(base)
5
+ base.send(:include, InstanceMethods)
6
+ base.send(:extend, ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ end # ClassMethods
11
+
12
+ module InstanceMethods
13
+
14
+ # Access to the authentication object directly. Particularly useful
15
+ # for accessing the errors.
16
+ #
17
+ # === Example
18
+ #
19
+ # <%= error_messages_for session.authentication %>
20
+ #
21
+ def authentication
22
+ @authentication ||= Merb::Authentication.new(self)
23
+ end
24
+
25
+ # Check to see if the current session is authenticated
26
+ # @return true if authenticated. false otherwise
27
+ def authenticated?
28
+ authentication.authenticated?
29
+ end
30
+
31
+ # Authenticates the session via the authentication object.
32
+ #
33
+ # See Merb::Authentication#authenticate for usage
34
+ def authenticate!(request, params, *rest)
35
+ authentication.authenticate!(request, params, *rest)
36
+ end
37
+
38
+ # Provides access to the currently authenticated user.
39
+ def user
40
+ authentication.user
41
+ end
42
+
43
+ # set the currently authenticated user manually
44
+ # Merb::Authentication#store_user should know how to store the object into the session
45
+ def user=(the_user)
46
+ authentication.user = the_user
47
+ end
48
+
49
+ # Remove the user from the session and clear all data.
50
+ def abandon!
51
+ authentication.abandon!
52
+ end
53
+
54
+ end # InstanceMethods
55
+
56
+ end # Session
57
+ end # Merb
@@ -0,0 +1,206 @@
1
+ module Merb
2
+ class Authentication
3
+ cattr_reader :strategies, :default_strategy_order, :registered_strategies
4
+ @@strategies, @@default_strategy_order, @@registered_strategies = [], [], {}
5
+
6
+ # Use this to set the default order of strategies
7
+ # if you need to in your application. You don't need to use all avaiable strategies
8
+ # in this array, but you may not include a strategy that has not yet been defined.
9
+ #
10
+ # @params [Merb::Authentiation::Strategy,Merb::Authentication::Strategy]
11
+ #
12
+ # @public
13
+ def self.default_strategy_order=(*order)
14
+ order = order.flatten
15
+ bad = order.select{|s| !s.ancestors.include?(Strategy)}
16
+ raise ArgumentError, "#{bad.join(",")} do not inherit from Merb::Authentication::Strategy" unless bad.empty?
17
+ @@default_strategy_order = order
18
+ end
19
+
20
+ # Allows for the registration of strategies.
21
+ # @params <Symbol, String>
22
+ # +label+ The label is the label to identify this strategy
23
+ # +path+ The path to the file containing the strategy. This must be an absolute path!
24
+ #
25
+ # Registering a strategy does not add it to the list of strategies to use
26
+ # it simply makes it available through the Merb::Authentication.activate method
27
+ #
28
+ # This is for plugin writers to make a strategy availalbe but this should not
29
+ # stop you from declaring your own strategies
30
+ #
31
+ # @plugin
32
+ def self.register(label, path)
33
+ self.registered_strategies[label] = path
34
+ end
35
+
36
+ # Activates a registered strategy by it's label.
37
+ # Intended for use with plugin authors. There is little
38
+ # need to register your own strategies. Just declare them
39
+ # and they will be active.
40
+ def self.activate!(label)
41
+ path = self.registered_strategies[label]
42
+ raise "The #{label} Strategy is not registered" unless path
43
+ require path
44
+ end
45
+
46
+ # The Merb::Authentication::Strategy is where all the action happens in the merb-auth framework.
47
+ # Inherit from this class to setup your own strategy. The strategy will automatically
48
+ # be placed in the default_strategy_order array, and will be included in the strategy runs.
49
+ #
50
+ # The strategy you implment should have a YourStrategy#run! method defined that returns
51
+ # 1. A user object if authenticated
52
+ # 2. nil if no authenticated user was found.
53
+ #
54
+ # === Example
55
+ #
56
+ # class MyStrategy < Merb::Authentication::Strategy
57
+ # def run!
58
+ # u = User.get(params[:login])
59
+ # u if u.authentic?(params[:password])
60
+ # end
61
+ # end
62
+ #
63
+ #
64
+ class Strategy
65
+ attr_accessor :request
66
+ attr_writer :body
67
+
68
+ class << self
69
+ def inherited(klass)
70
+ Merb::Authentication.strategies << klass
71
+ Merb::Authentication.default_strategy_order << klass
72
+ end
73
+
74
+ # Use this to declare the strategy should run before another strategy
75
+ def before(strategy)
76
+ order = Merb::Authentication.default_strategy_order
77
+ order.delete(self)
78
+ index = order.index(strategy)
79
+ order.insert(index,self)
80
+ end
81
+
82
+ # Use this to declare the strategy should run after another strategy
83
+ def after(strategy)
84
+ order = Merb::Authentication.default_strategy_order
85
+ order.delete(self)
86
+ index = order.index(strategy)
87
+ index == order.size ? order << self : order.insert(index + 1, self)
88
+ end
89
+
90
+ # Mark a strategy as abstract. This means that a strategy will not
91
+ # ever be run as part of the authentication. Instead this
92
+ # will be available to inherit from as a way to share code.
93
+ #
94
+ # You could for example setup a strategy to check for a particular kind of login
95
+ # and then have a subclass for each class type of user in your system.
96
+ # i.e. Customer / Staff, Student / Staff etc
97
+ def abstract!
98
+ @abstract = true
99
+ end
100
+
101
+ # Asks is this strategy abstract. i.e. can it be run as part of the authentication
102
+ def abstract?
103
+ !!@abstract
104
+ end
105
+
106
+ end # End class << self
107
+
108
+ def initialize(request, params)
109
+ @request = request
110
+ @params = params
111
+ end
112
+
113
+ # An alias to the request.params hash
114
+ # Only rely on this hash to find any router params you are looking for.
115
+ # If looking for paramteres use request.params
116
+ def params
117
+ @params
118
+ end
119
+
120
+ # An alials to the request.cookies hash
121
+ def cookies
122
+ request.cookies
123
+ end
124
+
125
+ # An alias to the request.session hash
126
+ def session
127
+ request.session
128
+ end
129
+
130
+ # Redirects causes the strategy to signal a redirect
131
+ # to the provided url.
132
+ #
133
+ # ====Parameters
134
+ # url<String>:: The url to redirect to
135
+ # options<Hash>:: An options hash with the following keys:
136
+ # +:permanent+ Set this to true to make the redirect permanent
137
+ # +:status+ Set this to an integer for the status to return
138
+ def redirect!(url, opts = {})
139
+ self.headers["Location"] = url
140
+ self.status = opts[:permanent] ? 301 : 302
141
+ self.status = opts[:status] if opts[:status]
142
+ self.body = opts[:message] || "<div>You are being redirected to <a href='#{url}'>#{url}</a></div>"
143
+ halt!
144
+ return true
145
+ end
146
+
147
+ # Returns ture if the strategy redirected
148
+ def redirected?
149
+ !!headers["Location"]
150
+ end
151
+
152
+ # Provides a place to put the status of the response
153
+ attr_accessor :status
154
+
155
+ # Provides a place to put headers
156
+ def headers
157
+ @headers ||={}
158
+ end
159
+
160
+ # Mark this strategy as complete for this request. Will cause that no other
161
+ # strategies will be executed.
162
+ def halt!
163
+ @halt = true
164
+ end
165
+
166
+ # Checks to see if this strategy has been halted
167
+ def halted?
168
+ !!@halt
169
+ end
170
+
171
+
172
+ # Allows you to provide a body of content to return when halting
173
+ def body
174
+ @body || ""
175
+ end
176
+
177
+ # This is the method that is called as the test for authentication and is where
178
+ # you put your code.
179
+ #
180
+ # You must overwrite this method in your strategy
181
+ #
182
+ # @api overwritable
183
+ def run!
184
+ raise NotImplemented
185
+ end
186
+
187
+ # Overwrite this method to scope a strategy to a particular user type
188
+ # you can use this with inheritance for example to try the same strategy
189
+ # on different user types
190
+ #
191
+ # By default, Merb::Authentication.user_class is used. This method allows for
192
+ # particular strategies to deal with a different type of user class.
193
+ #
194
+ # For example. If Merb::Authentication.user_class is Customer
195
+ # and you have a PasswordStrategy, you can subclass the PasswordStrategy
196
+ # and change this method to return Staff. Giving you a PasswordStrategy strategy
197
+ # for first Customer(s) and then Staff.
198
+ #
199
+ # @api overwritable
200
+ def user_class
201
+ Merb::Authentication.user_class
202
+ end
203
+
204
+ end # Strategy
205
+ end # Merb::Authentication
206
+ end # Merb
@@ -0,0 +1,26 @@
1
+ # make sure we're running inside Merb
2
+
3
+ require 'extlib'
4
+
5
+ require 'merb-auth-core/authentication'
6
+ require 'merb-auth-core/strategy'
7
+ require 'merb-auth-core/session_mixin'
8
+ require 'merb-auth-core/errors'
9
+ require 'merb-auth-core/responses'
10
+ require 'merb-auth-core/authenticated_helper'
11
+ require 'merb-auth-core/customizations'
12
+ require 'merb-auth-core/bootloader'
13
+ require 'merb-auth-core/router_helper'
14
+
15
+ Merb::BootLoader.before_app_loads do
16
+ # require code that must be loaded before the application
17
+ Merb::Controller.send(:include, Merb::AuthenticatedHelper)
18
+ end
19
+
20
+ Merb::BootLoader.after_app_loads do
21
+ # code that can be required after the application loads
22
+ end
23
+
24
+ Merb::Plugins.add_rakefiles "merb-auth-core/merbtasks"
25
+
26
+ Merb.push_path(:lib_authentication, Merb.root / "merb" / "merb-auth")
@@ -0,0 +1,111 @@
1
+ require File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
2
+
3
+ describe "Merb::AuthenticationHelper" do
4
+
5
+ class ControllerMock < Merb::Controller
6
+ before :ensure_authenticated
7
+ end
8
+
9
+ before(:each) do
10
+ clear_strategies!
11
+ @controller = ControllerMock.new(fake_request)
12
+ @request = @controller.request
13
+ @session = @controller.session
14
+ @session.user = "WINNA"
15
+ Viking.captures.clear
16
+
17
+ class Kone < Merb::Authentication::Strategy
18
+ def run!
19
+ puts params.inspect
20
+ Viking.capture(self.class)
21
+ params[self.class.name]
22
+ end
23
+ end
24
+
25
+ class Ktwo < Kone; end
26
+
27
+ end
28
+
29
+ it "should not raise and Unauthenticated error" do
30
+ lambda do
31
+ @controller.send(:ensure_authenticated)
32
+ end.should_not raise_error(Merb::Controller::Unauthenticated)
33
+ end
34
+
35
+ it "should raise an Unauthenticated error" do
36
+ @controller = ControllerMock.new(Merb::Request.new({}))
37
+ lambda do
38
+ @controller.send(:ensure_authenticated)
39
+ end.should raise_error(Merb::Controller::Unauthenticated)
40
+ end
41
+
42
+ it "should run the authentication when testing if it is authenticated" do
43
+ @controller = ControllerMock.new(fake_request)
44
+ @controller.session.should_receive(:user).and_return(nil, "WINNA")
45
+ @controller.session.authentication.should_receive(:authenticate!).and_return("WINNA")
46
+ @controller.send(:ensure_authenticated)
47
+ end
48
+
49
+ it "should accept and execute the provided strategies" do
50
+ # This allows using a before filter with specific arguments
51
+ # before :ensure_authenticated, :with => [Authenticaiton::OAuth, Merb::Authentication::BasicAuth]
52
+ controller = ControllerMock.new(fake_request)
53
+ controller.request.params["Ktwo"] = true
54
+ controller.send(:ensure_authenticated, Kone, Ktwo)
55
+ Viking.captures.should == %w( Kone Ktwo )
56
+ end
57
+
58
+ describe "redirection" do
59
+
60
+ before(:all) do
61
+ class FooController < Merb::Controller
62
+ before :ensure_authenticated
63
+
64
+ def index; "FooController#index" end
65
+ end
66
+ end
67
+
68
+ before(:each) do
69
+ class MyStrategy < Merb::Authentication::Strategy
70
+ def run!
71
+ if params[:url]
72
+ opts = {}
73
+ opts[:permanent] = true if params[:permanent]
74
+ opts[:status] = params[:status].to_i if params[:status]
75
+ !opts.empty? ? redirect!(params[:url], opts) : redirect!(params[:url])
76
+ elsif params[:fail]
77
+ nil
78
+ else
79
+ "WINNA"
80
+ end
81
+ end
82
+ end # MyStrategy
83
+
84
+ Merb::Router.reset!
85
+ Merb::Router.prepare{ match("/").to(:controller => "foo_controller")}
86
+ end
87
+
88
+ it "should redirect the controller to a Location if the strategy redirects" do
89
+ controller = get "/", :url => "/some/url"
90
+ controller.headers["Location"].should == "/some/url"
91
+ end
92
+
93
+ it "should use a 302 redirection by default" do
94
+ c = get "/", :url => "/some/url"
95
+ c.status.should == 302
96
+ end
97
+
98
+ it "should use a 301 when marked as permanent" do
99
+ c = get "/", :url => "/some/url", :permanent => "true"
100
+ c.status.should == 301
101
+ end
102
+
103
+ it "should use a custom status" do
104
+ c = get "/", :url => "/some/url", :status => 401
105
+ c.status.should == 401
106
+ end
107
+
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,2 @@
1
+ class TheActivationTest
2
+ end