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 +20 -0
- data/README.textile +338 -0
- data/Rakefile +65 -0
- data/TODO +0 -0
- data/lib/merb-auth-core/authenticated_helper.rb +42 -0
- data/lib/merb-auth-core/authentication.rb +130 -0
- data/lib/merb-auth-core/bootloader.rb +10 -0
- data/lib/merb-auth-core/customizations.rb +24 -0
- data/lib/merb-auth-core/errors.rb +66 -0
- data/lib/merb-auth-core/merbtasks.rb +6 -0
- data/lib/merb-auth-core/responses.rb +36 -0
- data/lib/merb-auth-core/router_helper.rb +25 -0
- data/lib/merb-auth-core/session_mixin.rb +57 -0
- data/lib/merb-auth-core/strategy.rb +206 -0
- data/lib/merb-auth-core.rb +26 -0
- data/spec/helpers/authentication_helper_spec.rb +111 -0
- data/spec/merb-auth-core/activation_fixture.rb +2 -0
- data/spec/merb-auth-core/authentication_spec.rb +318 -0
- data/spec/merb-auth-core/customizations_spec.rb +22 -0
- data/spec/merb-auth-core/errors_spec.rb +51 -0
- data/spec/merb-auth-core/merb-auth-core_spec.rb +15 -0
- data/spec/merb-auth-core/router_helper_spec.rb +114 -0
- data/spec/merb-auth-core/strategy_spec.rb +274 -0
- data/spec/spec_helper.rb +93 -0
- metadata +100 -0
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 < 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 < 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
|