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