merb-auth-core 0.9.9

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,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