merb-auth-core 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Adam French, Daniel Neighman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,338 @@
1
+ h2. merb-auth-core
2
+
3
+ MerbAuth is an authentication framework for use with the
4
+ Merb web framework.
5
+
6
+ MerbAuth does not try to dictate what you should use as a user model, or how
7
+ it should authenticate. Instead it focuses on the logic required to check
8
+ that an object passes authentication, and store authenticated objects in the
9
+ session. This is in fact the guiding principle of MerbAuth. The Session is
10
+ used as the place for authentication, with a sprinkling of controller helpers.
11
+ This makes sense to talk about an authenticated session. For example, inside
12
+ your controller:
13
+
14
+ * session.authenticated?
15
+ returns true if the session has been
16
+ authenticated. False otherwise # session.authenticate(controller)
17
+ authenticates the session based on customizable user defined rules
18
+
19
+ * session.user
20
+ returns the currently authenticated user object
21
+
22
+ * session.user=
23
+ manually sets the currently authenticated user object
24
+
25
+ * session.abandon!
26
+ sets the session to unauthenticated, and clears all session data
27
+
28
+ MerbAuth makes use of Merb's exception handling facilities which return correct
29
+ HTTP status codes when a 200 OK would be inappropriate. To fail a login, or
30
+ to force a login at any point in your controller code, simply raise an
31
+ Unauthenticated exception, with an optional message and the user will be
32
+ presented with login page. The login page is in fact the html view for
33
+ Extensions#unauthenticated
34
+
35
+ To protect your controllers, add a simple @before@ filter to your controller.
36
+
37
+ <code> before :ensure_authenticated </code>
38
+
39
+ It is possible to use MerbAuth with any object as a _user_ object, provided
40
+ that object does not evaluate to false and it can be serialized in an out of the session.
41
+ For this reason, merb-auth-core does not try to implement even a simple login
42
+ form for you, since it may not meet your requirements.
43
+
44
+ h3. How Does It Authenticate my arbitrary user?
45
+
46
+ This is very similar to the BootLoader process in Merbs initialization.
47
+ You declare a class that inherits from Merb::Authentication::Strategy and
48
+ define an instance method @run!@
49
+
50
+ <pre><code>
51
+ class PasswordStrategy &lt Merb::Authentication::Strategy
52
+
53
+ def run!
54
+ if params[:login] && params[:password]
55
+ user_class.authenticate(params[:login], params[:password])
56
+ end
57
+ end
58
+ end
59
+
60
+ </code></pre>
61
+
62
+ bq. This login strategy uses the @authenticate@ method on the User class to
63
+ retrieve a user by @login@ and @password@. Remember, you can put as much logic
64
+ here as you require. The strategy uses an instance variable so the full power of
65
+ classes are available to your strategy.
66
+
67
+ The strategy provides access to the current request giving you access
68
+ to the params hash, session etc.
69
+
70
+ To pass authentication, simply return a non-nil
71
+ non-false value from the @run!@ method. Any false or nil value will cause
72
+ that strategy to fail. Then the next strategy will be tried :) wait... what?
73
+
74
+ You can add as many strategies as you like and they will be tried one after
75
+ another until either one is found that works (login), or none of them have
76
+ passed (failed attempt).
77
+
78
+ <pre><code>
79
+ class PasswordLoginBasicAuth &lt Merb::Authentication::Strategy
80
+ def run!
81
+ if params[:api_key] && params[:api_token]
82
+ Machine.api_authenticate(params[:api_key], params[:api_token])
83
+ end
84
+ end
85
+ end
86
+ </code></pre>
87
+
88
+ Now that we have two, they will be executed in the order that they were declared
89
+ when we call @session.authenticate!(self)@. The first one that
90
+ returns a value that doesn't evaluate to false, will be considered the winner.
91
+
92
+ h3. Customizing the user_class
93
+
94
+ Notice the @user_class@ method in the above strategy examples. This is a convenience method on a strategy
95
+ to provide you with the user_class to use for this strategy. You can overwrite
96
+ this method on a per strategy basis to use different user model types.
97
+
98
+ By default the strategy#user_class method will defer to Merb::Authentication#user_class. You can
99
+ set which is the "default class" that Merb::Authentication will use in the provided strategies by
100
+ setting the class inside your model declaration.
101
+
102
+ <pre><code>
103
+ class Person
104
+ include DataMapper::Resource
105
+
106
+ Merb::Authentication.user_class = self
107
+
108
+ #...
109
+ end
110
+ </code></pre>
111
+
112
+ This will cascade throughout the default strategies, and your own strategies vai that
113
+ Merb::Authentication::Strategy#user_class method.
114
+
115
+ There is no default class set for Activation by default
116
+
117
+ h3. Strategies and Inheritance
118
+
119
+ Strategies may be inherited multiple times to make the job of combining similar aspects easier.
120
+ You can inherit as many levels as you like and at any point you may mark a strategy as _abstract_
121
+
122
+ An abstract strategy just means that it will not be run when it comes time to authenticate.
123
+ Instead it's good to put common logic in and then inherit from it to keep your strategies DRY.
124
+
125
+ To mark a class as abstract, use the @abstract!@ class method.
126
+
127
+ <pre><code>
128
+ class AbstractStrategy < Merb::Authentication::Strategy
129
+ abstract!
130
+ end
131
+ </code></pre>
132
+
133
+ At any point you can activate a registered strategy. You don't need to register
134
+ your strategies, you just declare them, but plugin developers make life easier when they do.
135
+
136
+ To activate a registered strategy:
137
+ <pre><code>
138
+ Merb::Authentication.activate!(:defualt_password_form)
139
+ </code></pre>
140
+
141
+ You can easily mix this in with your own strategies. In you lib/authentication/strategies.rb
142
+ <pre><code>
143
+ class MyStrategy < Merb::Authentication::Strategy
144
+ def run!
145
+ #...
146
+ end
147
+ end
148
+
149
+ Merb::Authentication.activate!(:default_openid)
150
+
151
+ class AnotherStrategy < Merb::Authentication::Strategy
152
+ def run!
153
+ #...
154
+ end
155
+ end
156
+ </code></pre>
157
+
158
+ This will collect them in order of decleration. i.e.:
159
+ MyStrategy, Merb::Authentication::Strategies::Basic::OpenID, AnotherStrategy
160
+
161
+ h3. Customizing the order of the strategies
162
+
163
+ By default, strategies are run in the order they are declared. It's possible
164
+ to customize the order that the strategies are called.
165
+
166
+ @Merb::Authentication.default_strategy_order@ will return an array of
167
+ the strategy classes in the order that they will be run.
168
+ You can customize this by setting the default_strategy_order array
169
+ manually.
170
+
171
+ @Authenticateion.default_strategy_order.order = [Second, First, Fourth]@
172
+
173
+ It's possible to leave some out, and re-order existing ones. It will error
174
+ out if you specify one that doesn't exist though.
175
+
176
+ h3. Specifying selected strategies per action
177
+
178
+ It's possible to configure each call to @ensure_authenticated@ with a custom list
179
+ of strategies to run. These will be run in order and should have an instance method
180
+ of #run!
181
+
182
+ <pre><code>
183
+ class ApiMethods < Application
184
+ before :ensure_authenticated, :with => [
185
+ Merb::Authenticated::Strategies::Basic::Form,
186
+ Merb::Authenticated::Strategies::Basic::BasicAuth,
187
+ Merb::Authenticated::Strategies::Basic::OpenID,
188
+ ]
189
+ before :machine_only, :only => [:create]
190
+
191
+ def index
192
+ display @stuff
193
+ end
194
+
195
+ def create
196
+ stuff = Stuff.create(params[:stuff])
197
+ display stuff
198
+ end
199
+
200
+ private
201
+ def machine_only
202
+ ensure_authentiated Merb::Authenticated::Strategies::Basic::OAuth, Merb::Authenticated::Strategies::Basic::BasicAuth
203
+ end
204
+ end
205
+ </code></pre>
206
+
207
+ You can see in this example that you can specify a list of strategies to use.
208
+ These will be executed in the order of the array passed in, with the default order
209
+ ignored completely.
210
+
211
+ h3. Where should Strategies be defined?
212
+
213
+ You should store your strategies in
214
+ <pre><code>
215
+ lib
216
+ `-- authentication
217
+ |-- setup.rb
218
+ `-- strategies.rb
219
+ </code></pre>
220
+
221
+ This is a good place to put everything together so you can see what you're doing at a glance.
222
+
223
+ h3. What Strategies are there?
224
+
225
+ See merb-auth-more
226
+
227
+ h3. Storing you user object into the session
228
+
229
+ You need to tell MerbAuth how to serialize your object into
230
+ and out of the session. If possible try not to store large or complex
231
+ data items in the session.
232
+
233
+ To configure your user object to go in and out of the session, here's how you
234
+ could do it.
235
+
236
+ <pre><code>
237
+ class Merb::Authentication
238
+
239
+ # return the value you want stored in the session
240
+ def store_user(user)
241
+ return nil unless user
242
+ user.id
243
+ end
244
+
245
+ # session info is the data you stored in the session previously
246
+ def fetch_user(session_info)
247
+ User.get(session_info)
248
+ end
249
+ end
250
+ </code></pre>
251
+
252
+ h3. Registering Strategies
253
+
254
+ Intended for plugin developers as a way to make it easy to use
255
+ strategies there is the possibility to register a strategy without loading it.
256
+
257
+ <pre><code>
258
+ Authentication.register(:my_strategy, "/absolute/path/to/strategy.rb")
259
+ </code></pre>
260
+
261
+ This then allows developers to use
262
+
263
+ <pre><code>
264
+ Authentication.activate!(:my_strategy)
265
+ </pre></code>
266
+
267
+ h3. Providing feedback to users (Error Messages)
268
+
269
+ There's at least 4 ways to provide feedback to users for failed logins.
270
+
271
+ * Overwrite Merb::Authentication#error_message The return of this method is
272
+ the default message that is passed to the Unauthenticated exception. Overwrite
273
+ this to provide a very basic catch all message.
274
+ * Provide a default message when you declare your before filter.
275
+ <pre><code>
276
+ before :ensure_authenticated, :with => [Openid, :message => "Could not log you in with open ID"]
277
+ # OR
278
+ before :ensure_authentication, :with => {:message => "Sorry Buddy... You Lose"}
279
+ </code></pre>
280
+ When you pass a message, it will replace the Merb::Authentication#error_message default for this
281
+ action
282
+ * Use an after filter for your login action. This can be used to set your messaging system. For example:
283
+ <pre><code>
284
+ after :set_login_message, :only => [:create]
285
+
286
+ private
287
+ def set_login_message
288
+ if session.authenticated?
289
+ flash[:message] = "Welcome"
290
+ else
291
+ flash[:error] = "Bad.. You Fail"
292
+ end
293
+ end
294
+ </code></pre>
295
+ * Use the authentications error messaging inside your strategies to set error messages there.
296
+ You can add to these errors just like adding to DataMappers validation errors.
297
+
298
+ <pre><code>
299
+ session.authentication.errors.add("Label", "You Fail")
300
+ </code></pre>
301
+ Add as many as you like, ask @session.authentication.errors.on(:label)@ to get specific errors etc
302
+ Really... They're just like the DataMapper validation errors. The bonus of using this system
303
+ is that you can add messages inside your Strategies, and then in your views you can do this:
304
+ <pre><code>
305
+ <%= error_messages_for sessions.authentication %>
306
+ </pre></code>
307
+
308
+
309
+ h3. Additional checks / actions to perform after the user is found
310
+
311
+ Sometimes you may need to perform additional operations on the user object
312
+ before or after you grab it out of the database when authenticating it. The
313
+ Merb::Authentication class implements Extlib::Hook so you can just setup hooks to
314
+ deal with this.
315
+
316
+ Here's an example of checking that a user object is active after it's been
317
+ found:
318
+
319
+ after :authenticate! do |instance, *args|
320
+ raise Merb::Controller::Unauthenticated, "User Not Active" unless instance.user.active?
321
+ end
322
+
323
+ bq. Notice that to fail the check we raised an Unauthenticated exception. The
324
+ session is available in that block as <code>session</code>
325
+
326
+ Really that's all there is to it. By default this plugin doesn't actually
327
+ authenticate anything ;) It's up to you to get your model going, and add an
328
+ authentication strategy. Just remember that to login, you just use
329
+ @session.authenticate(request)@ inside a controller. To logout use
330
+ @session.abandon!@ and to force a login at any time use
331
+ @raise Unauthenticated, "You Aren't Cool Enough"@
332
+ Be aware that strategies may throw :halt for use as a before filter...
333
+
334
+ h3. Contributors
335
+
336
+ # Adam French - "http://adam.speaksoutofturn.com/":http://adam.speaksoutofturn.com/
337
+ # Daniel Neighman - "http://merbunity.com":http://merbunity.com
338
+ # Ben Burket - "http://benburkert.com/":http://benburkert.com/
data/Rakefile ADDED
@@ -0,0 +1,65 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require "spec/rake/spectask"
6
+ require File.join(File.dirname(__FILE__), "../../merb-core/lib/merb-core/version")
7
+ require File.join(File.dirname(__FILE__), "../../merb-core/lib/merb-core/tasks/merb_rake_helper")
8
+ require 'rake/testtask'
9
+
10
+
11
+ GEM_NAME = "merb-auth-core"
12
+ GEM_VERSION = Merb::VERSION
13
+ AUTHOR = "Adam French, Daniel Neighman"
14
+ EMAIL = "has.sox@gmail.com"
15
+ HOMEPAGE = "http://merbivore.com/"
16
+ SUMMARY = "An Authentication framework for Merb"
17
+
18
+ spec = Gem::Specification.new do |s|
19
+ s.rubyforge_project = 'merb'
20
+ s.name = GEM_NAME
21
+ s.version = GEM_VERSION
22
+ s.platform = Gem::Platform::RUBY
23
+ s.has_rdoc = true
24
+ s.extra_rdoc_files = ["README.textile", "LICENSE", "TODO"]
25
+ s.summary = SUMMARY
26
+ s.description = s.summary
27
+ s.author = AUTHOR
28
+ s.email = EMAIL
29
+ s.homepage = HOMEPAGE
30
+ s.add_dependency('merb-core', '~>0.9.9')
31
+ s.add_dependency('extlib')
32
+ s.require_path = 'lib'
33
+ s.files = %w(LICENSE README.textile Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
34
+
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |pkg|
38
+ pkg.gem_spec = spec
39
+ end
40
+
41
+ desc "install the plugin as a gem"
42
+ task :install do
43
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
44
+ end
45
+
46
+ desc "Uninstall the gem"
47
+ task :uninstall do
48
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
49
+ end
50
+
51
+ desc "Create a gemspec file"
52
+ task :gemspec do
53
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
54
+ file.puts spec.to_ruby
55
+ end
56
+ end
57
+
58
+ desc "Run all specs"
59
+ Spec::Rake::SpecTask.new("specs") do |t|
60
+ t.spec_opts = ["--format", "specdoc", "--colour"]
61
+ t.spec_files = Dir["spec/**/*_spec.rb"].sort
62
+ t.rcov = false
63
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
64
+ t.rcov_opts << '--only-uncovered'
65
+ end
data/TODO ADDED
File without changes
@@ -0,0 +1,42 @@
1
+ module Merb
2
+ class Controller::Unauthenticated < ControllerExceptions::Unauthorized; end
3
+
4
+ module AuthenticatedHelper
5
+ protected
6
+ # This is the main method to use as a before filter. You can call it with options
7
+ # and strategies to use. It will check if a user is logged in, and failing that
8
+ # will run through either specified.
9
+ #
10
+ # @params all are optional. A list of strategies, optionally followed by a
11
+ # options hash.
12
+ #
13
+ # If used with no options, or only the hash, the default strategies will be used
14
+ # see Authentictaion.default_strategy_order.
15
+ #
16
+ # If a list of strategies is passed in, the default strategies are ignored, and
17
+ # the passed in strategies are used in order until either one is found, or all fail.
18
+ #
19
+ # A failed login will result in an Unauthenticated exception being raised.
20
+ #
21
+ # Use the :message key in the options hash to pass in a failure message to the
22
+ # exception.
23
+ #
24
+ # === Example
25
+ #
26
+ # class MyController < Application
27
+ # before :ensure_authenticated, :with => [OpenID,FormPassword, :message => "Failz!"]
28
+ # #... <snip>
29
+ # end
30
+ #
31
+ def ensure_authenticated(*strategies)
32
+ session.authenticate!(request, params, *strategies) unless session.user
33
+ auth = session.authentication
34
+ if auth.halted?
35
+ self.headers.merge!(auth.headers)
36
+ self.status = auth.status
37
+ throw :halt, auth.body
38
+ end
39
+ session.user
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,130 @@
1
+ module Merb
2
+ class Authentication
3
+ include Extlib::Hook
4
+ attr_accessor :session
5
+ attr_writer :error_message
6
+
7
+ class DuplicateStrategy < Exception; end
8
+ class MissingStrategy < Exception; end
9
+ class NotImplemented < Exception; end
10
+
11
+ # This method returns the default user class to use throughout the
12
+ # merb-auth authentication framework. Merb::Authentication.user_class can
13
+ # be used by other plugins, and by default by strategies.
14
+ #
15
+ # By Default it is set to User class. If you need a different class
16
+ # The intention is that you overwrite this method
17
+ #
18
+ # @return <User Class Object>
19
+ #
20
+ # @api overwritable
21
+ cattr_accessor :user_class
22
+
23
+ def initialize(session)
24
+ @session = session
25
+ end
26
+
27
+ # Returns true if there is an authenticated user attached to this session
28
+ #
29
+ # @return <TrueClass|FalseClass>
30
+ #
31
+ def authenticated?
32
+ !!user
33
+ end
34
+
35
+ # This method will retrieve the user object stored in the session or nil if there
36
+ # is no user logged in.
37
+ #
38
+ # @return <User class>|NilClass
39
+ def user
40
+ return nil if !session[:user]
41
+ @user ||= fetch_user(session[:user])
42
+ end
43
+
44
+ # This method will store the user provided into the session
45
+ # and set the user as the currently logged in user
46
+ # @return <User Class>|NilClass
47
+ def user=(user)
48
+ session[:user] = nil && return if user.nil?
49
+ session[:user] = store_user(user)
50
+ @user = session[:user] ? user : session[:user]
51
+ end
52
+
53
+ # The workhorse of the framework. The authentiate! method is where
54
+ # the work is done. authenticate! will try each strategy in order
55
+ # either passed in, or in the default_strategy_order.
56
+ #
57
+ # If a strategy returns some kind of user object, this will be stored
58
+ # in the session, otherwise a Merb::Controller::Unauthenticated exception is raised
59
+ #
60
+ # @params Merb::Request, [List,Of,Strategies, optional_options_hash]
61
+ #
62
+ # Pass in a list of strategy objects to have this list take precedence over the normal defaults
63
+ #
64
+ # Use an options hash to provide an error message to be passed into the exception.
65
+ #
66
+ # @return user object of the verified user. An exception is raised if no user is found
67
+ #
68
+ def authenticate!(request, params, *rest)
69
+ opts = rest.last.kind_of?(Hash) ? rest.pop : {}
70
+ rest = rest.flatten
71
+ strategies = rest.empty? ? Merb::Authentication.default_strategy_order : rest
72
+
73
+ msg = opts[:message] || error_message
74
+ user = nil
75
+ # This one should find the first one that matches. It should not run antother
76
+ strategies.detect do |s|
77
+ unless s.abstract?
78
+ strategy = s.new(request, params)
79
+ user = strategy.run!
80
+ if strategy.halted?
81
+ self.headers = strategy.headers
82
+ self.status = strategy.status
83
+ self.body = strategy.body
84
+ halt!
85
+ return
86
+ end
87
+ user
88
+ end
89
+ end
90
+ raise Merb::Controller::Unauthenticated, msg unless user
91
+ self.user = user
92
+ end
93
+
94
+ # "Logs Out" a user from the session. Also clears out all session data
95
+ def abandon!
96
+ @user = nil
97
+ session.clear
98
+ end
99
+
100
+ # A simple error message mechanism to provide general information. For more specific information
101
+ #
102
+ # This message is the default message passed to the Merb::Controller::Unauthenticated exception
103
+ # during authentication.
104
+ #
105
+ # This is a very simple mechanism for error messages. For more detailed control see Authenticaiton#errors
106
+ #
107
+ # @api overwritable
108
+ def error_message
109
+ @error_message || "Could not log in"
110
+ end
111
+
112
+ # Tells the framework how to store your user object into the session so that it can be re-created
113
+ # on the next login.
114
+ # You must overwrite this method for use in your projects. Slices and plugins may set this.
115
+ #
116
+ # @api overwritable
117
+ def store_user(user)
118
+ raise NotImplemented
119
+ end
120
+
121
+ # Tells the framework how to reconstitute a user from the data stored by store_user.
122
+ #
123
+ # You must overwrite this method for user in your projects. Slices and plugins may set this.
124
+ #
125
+ # @api overwritable
126
+ def fetch_user(session_contents = session[:user])
127
+ raise NotImplemented
128
+ end
129
+ end # Merb::Authentication
130
+ end # Merb
@@ -0,0 +1,10 @@
1
+ # This is not intended to be modified. It is for use with
2
+ # Merb::Authentication.default_customizations
3
+ class Merb::BootLoader::MerbAuthBootLoader < Merb::BootLoader
4
+ before Merb::BootLoader::AfterAppLoads
5
+
6
+ def self.run
7
+ Merb::Authentication.default_customizations.each { |c| c.call }
8
+ end
9
+
10
+ end
@@ -0,0 +1,24 @@
1
+ module Merb
2
+ class Authentication
3
+
4
+ class << self
5
+
6
+ # Adds any customizations just before the after_app_loads boot loader
7
+ # so that plugins can offer default customization. This will still allow
8
+ # for a user to overwrite any customizations if required in the after_app_loads
9
+ # block
10
+ # @plugin
11
+ def customize_default(&block)
12
+ default_customizations << block
13
+ default_customizations
14
+ end
15
+
16
+ # Gets the list of declared customizations
17
+ def default_customizations
18
+ @custom_default_blocks ||= []
19
+ end
20
+
21
+ end
22
+
23
+ end # Merb::Authentication
24
+ end # Merb