myobie-rails-auth 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +41 -0
- data/VERSION.yml +4 -0
- data/lib/rails-auth/authenticated_helper.rb +23 -0
- data/lib/rails-auth/authentication.rb +210 -0
- data/lib/rails-auth/callbacks.rb +13 -0
- data/lib/rails-auth/errors.rb +66 -0
- data/lib/rails-auth/helpers/all.rb +3 -0
- data/lib/rails-auth/helpers/current_user.rb +20 -0
- data/lib/rails-auth/helpers/logged_in.rb +15 -0
- data/lib/rails-auth/helpers/redirect_back.rb +18 -0
- data/lib/rails-auth/helpers/require_login_or.rb +12 -0
- data/lib/rails-auth/mash.rb +148 -0
- data/lib/rails-auth/responses.rb +36 -0
- data/lib/rails-auth/session_mixin.rb +43 -0
- data/lib/rails-auth/strategies/abstract_password.rb +31 -0
- data/lib/rails-auth/strategies/password_form.rb +36 -0
- data/lib/rails-auth/strategy.rb +206 -0
- data/lib/rails-auth.rb +21 -0
- data/test/rails-auth_test.rb +7 -0
- data/test/test_helper.rb +9 -0
- metadata +76 -0
data/README.markdown
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
Note
|
2
|
+
====
|
3
|
+
|
4
|
+
This is not anywhere near done. It's still using swaths of code just copied straight over from merb, and even some of that doesn't do anything yet.
|
5
|
+
|
6
|
+
About
|
7
|
+
=====
|
8
|
+
|
9
|
+
I am not satisfied with any of the current authentication strategies available for rails. I am sure Rails 3 will have something built in, but until then this is what I am going to use. I am spoiled from the way Merb does it, but even then I am not 100% happy with how it works.
|
10
|
+
|
11
|
+
Currently, this is untested and very much a copy of how merb does it.
|
12
|
+
|
13
|
+
All I really care about:
|
14
|
+
|
15
|
+
* Authenticate the session
|
16
|
+
* Authentication strategies
|
17
|
+
* Let me use my own model how I want
|
18
|
+
* Let me do my own controller how I want
|
19
|
+
* Be easy to setup (as an initializer)
|
20
|
+
* Don't do anything magical
|
21
|
+
|
22
|
+
I hate how all the authentication libraries hide model, controller, view methods from you down in the gem file. I also hate the generators that are impossible to use to update from one version to the next.
|
23
|
+
|
24
|
+
In my mind, the best way to solve this is to not have a ton of necessary methods on anything.
|
25
|
+
|
26
|
+
All you need
|
27
|
+
============
|
28
|
+
|
29
|
+
1. UserModel#authenticate
|
30
|
+
2. Use `ensure_authenticated` in your controller to make sure they are logged in.
|
31
|
+
3. rescue_from Rails::Authentication::Unauthenticated to catch when they are not logged in.
|
32
|
+
4. Setup an initializer that sets the UserModel class, etc
|
33
|
+
|
34
|
+
Simple.
|
35
|
+
|
36
|
+
TODO
|
37
|
+
====
|
38
|
+
|
39
|
+
* Tests
|
40
|
+
* Remove Mash
|
41
|
+
* Decide if the status, body, etc should be set by the authentication gem or not (right now it's kinda both)
|
data/VERSION.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rails
|
2
|
+
|
3
|
+
class Authentication
|
4
|
+
class Unauthenticated < Exception; end
|
5
|
+
end
|
6
|
+
|
7
|
+
module AuthenticatedHelper
|
8
|
+
|
9
|
+
protected
|
10
|
+
def ensure_authenticated(*strategies)
|
11
|
+
session.authenticate!(request, params, *strategies) unless session.authenticated?
|
12
|
+
auth = session.authentication
|
13
|
+
if auth.halted?
|
14
|
+
response.headers.merge!(auth.headers)
|
15
|
+
response.status = auth.status
|
16
|
+
raise Rails::Authentication::Unauthenticated, auth.body
|
17
|
+
end
|
18
|
+
session.user
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
module Rails
|
2
|
+
class Authentication
|
3
|
+
module Strategies; end
|
4
|
+
|
5
|
+
attr_accessor :session
|
6
|
+
attr_writer :error_message
|
7
|
+
|
8
|
+
class DuplicateStrategy < Exception; end
|
9
|
+
class MissingStrategy < Exception; end
|
10
|
+
class NotImplemented < Exception; end
|
11
|
+
|
12
|
+
# This method returns the default user class to use throughout the
|
13
|
+
# merb-auth authentication framework. Rails::Authentication.user_class can
|
14
|
+
# be used by other plugins, and by default by strategies.
|
15
|
+
#
|
16
|
+
# By Default it is set to User class. If you need a different class
|
17
|
+
# The intention is that you overwrite this method
|
18
|
+
#
|
19
|
+
# @return <User Class Object>
|
20
|
+
#
|
21
|
+
# @api overwritable
|
22
|
+
cattr_accessor :user_class
|
23
|
+
|
24
|
+
|
25
|
+
### copied from restful authentication
|
26
|
+
cattr_accessor :login_regex, :bad_login_message, :name_regex, :bad_name_message, :email_name_regex, :domain_head_regex, :domain_tld_regex, :email_regex, :bad_email_message
|
27
|
+
|
28
|
+
self.login_regex = /\A\w[\w\.\-_@]+\z/ # ASCII, strict
|
29
|
+
# self.login_regex = /\A[[:alnum:]][[:alnum:]\.\-_@]+\z/ # Unicode, strict
|
30
|
+
# self.login_regex = /\A[^[:cntrl:]\\<>\/&]*\z/ # Unicode, permissive
|
31
|
+
|
32
|
+
self.bad_login_message = "use only letters, numbers, and .-_@ please.".freeze
|
33
|
+
|
34
|
+
self.name_regex = /\A[^[:cntrl:]\\<>\/&]*\z/ # Unicode, permissive
|
35
|
+
self.bad_name_message = "avoid non-printing characters and \\><&/ please.".freeze
|
36
|
+
|
37
|
+
self.email_name_regex = '[\w\.%\+\-]+'.freeze
|
38
|
+
self.domain_head_regex = '(?:[A-Z0-9\-]+\.)+'.freeze
|
39
|
+
self.domain_tld_regex = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'.freeze
|
40
|
+
self.email_regex = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
|
41
|
+
self.bad_email_message = "should look like an email address.".freeze
|
42
|
+
|
43
|
+
def self.load_helpers(file_name)
|
44
|
+
require 'rails-auth/helpers' / file_name.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(session)
|
48
|
+
@session = session
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true if there is an authenticated user attached to this session
|
52
|
+
#
|
53
|
+
# @return <TrueClass|FalseClass>
|
54
|
+
#
|
55
|
+
def authenticated?
|
56
|
+
!!session[:user]
|
57
|
+
end
|
58
|
+
|
59
|
+
# This method will retrieve the user object stored in the session or nil if there
|
60
|
+
# is no user logged in.
|
61
|
+
#
|
62
|
+
# @return <User class>|NilClass
|
63
|
+
def user
|
64
|
+
return nil if !session[:user]
|
65
|
+
@user ||= fetch_user(session[:user])
|
66
|
+
end
|
67
|
+
|
68
|
+
# This method will store the user provided into the session
|
69
|
+
# and set the user as the currently logged in user
|
70
|
+
# @return <User Class>|NilClass
|
71
|
+
def user=(user)
|
72
|
+
session[:user] = nil && return if user.nil?
|
73
|
+
session[:user] = store_user(user)
|
74
|
+
@user = session[:user] ? user : session[:user]
|
75
|
+
end
|
76
|
+
|
77
|
+
# The workhorse of the framework. The authentiate! method is where
|
78
|
+
# the work is done. authenticate! will try each strategy in order
|
79
|
+
# either passed in, or in the default_strategy_order.
|
80
|
+
#
|
81
|
+
# If a strategy returns some kind of user object, this will be stored
|
82
|
+
# in the session, otherwise a Rails::Controller::Unauthenticated exception is raised
|
83
|
+
#
|
84
|
+
# @params Rails::Request, [List,Of,Strategies, optional_options_hash]
|
85
|
+
#
|
86
|
+
# Pass in a list of strategy objects to have this list take precedence over the normal defaults
|
87
|
+
#
|
88
|
+
# Use an options hash to provide an error message to be passed into the exception.
|
89
|
+
#
|
90
|
+
# @return user object of the verified user. An exception is raised if no user is found
|
91
|
+
#
|
92
|
+
def authenticate!(request, params, *rest)
|
93
|
+
opts = rest.last.kind_of?(Hash) ? rest.pop : {}
|
94
|
+
rest = rest.flatten
|
95
|
+
|
96
|
+
strategies = if rest.empty?
|
97
|
+
if request.session[:authentication_strategies]
|
98
|
+
request.session[:authentication_strategies]
|
99
|
+
else
|
100
|
+
Rails::Authentication.default_strategy_order
|
101
|
+
end
|
102
|
+
else
|
103
|
+
request.session[:authentication_strategies] ||= []
|
104
|
+
request.session[:authentication_strategies] << rest
|
105
|
+
request.session[:authentication_strategies].flatten!.uniq!
|
106
|
+
request.session[:authentication_strategies]
|
107
|
+
end
|
108
|
+
|
109
|
+
msg = opts[:message] || error_message
|
110
|
+
user = nil
|
111
|
+
# This one should find the first one that matches. It should not run any other
|
112
|
+
strategies.detect do |s|
|
113
|
+
s = Rails::Authentication.lookup_strategy[s] # Get the strategy from string or class
|
114
|
+
unless s.abstract?
|
115
|
+
strategy = s.new(request, params)
|
116
|
+
user = strategy.run!
|
117
|
+
if strategy.halted?
|
118
|
+
self.headers, self.status, self.body = [strategy.headers, strategy.status, strategy.body]
|
119
|
+
halt!
|
120
|
+
return
|
121
|
+
end
|
122
|
+
user
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Finally, Raise an error if there is no user found, or set it in the session if there is.
|
127
|
+
raise Rails::Authentication::Unauthenticated, msg unless user
|
128
|
+
session[:authentication_strategies] = nil # clear the session of Failed Strategies if login is successful
|
129
|
+
self.user = user
|
130
|
+
end
|
131
|
+
|
132
|
+
# "Logs Out" a user from the session. Also clears out all session data
|
133
|
+
def abandon!
|
134
|
+
@user = nil
|
135
|
+
session.clear
|
136
|
+
end
|
137
|
+
|
138
|
+
# A simple error message mechanism to provide general information. For more specific information
|
139
|
+
#
|
140
|
+
# This message is the default message passed to the Rails::Controller::Unauthenticated exception
|
141
|
+
# during authentication.
|
142
|
+
#
|
143
|
+
# This is a very simple mechanism for error messages. For more detailed control see Authenticaiton#errors
|
144
|
+
#
|
145
|
+
# @api overwritable
|
146
|
+
def error_message
|
147
|
+
@error_message || "Could not log in"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Tells the framework how to store your user object into the session so that it can be re-created
|
151
|
+
# on the next login.
|
152
|
+
# You must overwrite this method for use in your projects. Slices and plugins may set this.
|
153
|
+
#
|
154
|
+
# @api overwritable
|
155
|
+
def store_user(user)
|
156
|
+
raise NotImplemented
|
157
|
+
end
|
158
|
+
|
159
|
+
# Tells the framework how to reconstitute a user from the data stored by store_user.
|
160
|
+
#
|
161
|
+
# You must overwrite this method for user in your projects. Slices and plugins may set this.
|
162
|
+
#
|
163
|
+
# @api overwritable
|
164
|
+
def fetch_user(session_contents = session[:user])
|
165
|
+
raise NotImplemented
|
166
|
+
end
|
167
|
+
|
168
|
+
# Keeps track of strategies by class or string
|
169
|
+
# When loading from string, strategies are loaded withing the Rails::Authentication::Strategies namespace
|
170
|
+
# When loaded by class, the class is stored directly
|
171
|
+
# @private
|
172
|
+
def self.lookup_strategy
|
173
|
+
@strategy_lookup || reset_strategy_lookup!
|
174
|
+
end
|
175
|
+
|
176
|
+
# Restets the strategy lookup. Useful in specs
|
177
|
+
def self.reset_strategy_lookup!
|
178
|
+
@strategy_lookup = Mash.new do |h,k|
|
179
|
+
case k
|
180
|
+
when Class
|
181
|
+
h[k] = k
|
182
|
+
when String, Symbol
|
183
|
+
h[k] = Rails::Authentication::Strategies.full_const_get(k.to_s)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Maintains a list of keys to maintain when needing to keep some state
|
189
|
+
# in the face of session.abandon! You need to maintain this state yourself
|
190
|
+
# @public
|
191
|
+
def self.maintain_session_keys
|
192
|
+
@maintain_session_keys ||= [:authentication_strategies, :return_to]
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
def run_after_authentication_callbacks(user, request, params)
|
197
|
+
Rails::Authentication.after_callbacks.each do |cb|
|
198
|
+
user = case cb
|
199
|
+
when Proc
|
200
|
+
cb.call(user, request, params)
|
201
|
+
when Symbol, String
|
202
|
+
user.send(cb)
|
203
|
+
end
|
204
|
+
break unless user
|
205
|
+
end
|
206
|
+
user
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Rails
|
2
|
+
class Authentication
|
3
|
+
|
4
|
+
cattr_accessor :after_callbacks
|
5
|
+
@@after_callbacks = []
|
6
|
+
|
7
|
+
def self.after_authentication(*callbacks, &block)
|
8
|
+
self.after_callbacks = after_callbacks + callbacks.flatten unless callbacks.blank?
|
9
|
+
after_callbacks << block if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Rails
|
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 # Rails
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rails
|
2
|
+
|
3
|
+
module AuthenticatedControllerExtensions
|
4
|
+
|
5
|
+
def included(base)
|
6
|
+
base.send :helper_method, :current_user
|
7
|
+
base.send :helper_method, :current_user?
|
8
|
+
end
|
9
|
+
|
10
|
+
def current_user
|
11
|
+
session.user
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_user?
|
15
|
+
!!current_user
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rails
|
2
|
+
module AuthenticatedControllerExtensions
|
3
|
+
|
4
|
+
def redirect_back_or(default_url, opts = {})
|
5
|
+
if !session[:return_to].blank?
|
6
|
+
redirect_to session[:return_to], opts
|
7
|
+
session[:return_to] = nil
|
8
|
+
else
|
9
|
+
redirect_to default_url, opts
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_return_to
|
14
|
+
session[:return_to] = request.path
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Rails
|
2
|
+
|
3
|
+
module AuthenticatedControllerExtensions
|
4
|
+
|
5
|
+
def require_login_or(flash_type = nil, flash_message = nil)
|
6
|
+
flash[flash_type] = flash_message unless flash_message.blank? || flash_type.blank? || session.authenticated?
|
7
|
+
ensure_authenticated
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# This class has dubious semantics and we only have it so that people can write
|
2
|
+
# params[:key] instead of params['key'].
|
3
|
+
class Mash < Hash
|
4
|
+
|
5
|
+
# @param constructor<Object>
|
6
|
+
# The default value for the mash. Defaults to an empty hash.
|
7
|
+
#
|
8
|
+
# @details [Alternatives]
|
9
|
+
# If constructor is a Hash, a new mash will be created based on the keys of
|
10
|
+
# the hash and no default value will be set.
|
11
|
+
def initialize(constructor = {})
|
12
|
+
if constructor.is_a?(Hash)
|
13
|
+
super()
|
14
|
+
update(constructor)
|
15
|
+
else
|
16
|
+
super(constructor)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param key<Object> The default value for the mash. Defaults to nil.
|
21
|
+
#
|
22
|
+
# @details [Alternatives]
|
23
|
+
# If key is a Symbol and it is a key in the mash, then the default value will
|
24
|
+
# be set to the value matching the key.
|
25
|
+
def default(key = nil)
|
26
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
27
|
+
self[key]
|
28
|
+
else
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
34
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
35
|
+
|
36
|
+
# @param key<Object> The key to set.
|
37
|
+
# @param value<Object>
|
38
|
+
# The value to set the key to.
|
39
|
+
#
|
40
|
+
# @see Mash#convert_key
|
41
|
+
# @see Mash#convert_value
|
42
|
+
def []=(key, value)
|
43
|
+
regular_writer(convert_key(key), convert_value(value))
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param other_hash<Hash>
|
47
|
+
# A hash to update values in the mash with. The keys and the values will be
|
48
|
+
# converted to Mash format.
|
49
|
+
#
|
50
|
+
# @return <Mash> The updated mash.
|
51
|
+
def update(other_hash)
|
52
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
alias_method :merge!, :update
|
57
|
+
|
58
|
+
# @param key<Object> The key to check for. This will be run through convert_key.
|
59
|
+
#
|
60
|
+
# @return <TrueClass, FalseClass> True if the key exists in the mash.
|
61
|
+
def key?(key)
|
62
|
+
super(convert_key(key))
|
63
|
+
end
|
64
|
+
|
65
|
+
# def include? def has_key? def member?
|
66
|
+
alias_method :include?, :key?
|
67
|
+
alias_method :has_key?, :key?
|
68
|
+
alias_method :member?, :key?
|
69
|
+
|
70
|
+
# @param key<Object> The key to fetch. This will be run through convert_key.
|
71
|
+
# @param *extras<Array> Default value.
|
72
|
+
#
|
73
|
+
# @return <Object> The value at key or the default value.
|
74
|
+
def fetch(key, *extras)
|
75
|
+
super(convert_key(key), *extras)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param *indices<Array>
|
79
|
+
# The keys to retrieve values for. These will be run through +convert_key+.
|
80
|
+
#
|
81
|
+
# @return <Array> The values at each of the provided keys
|
82
|
+
def values_at(*indices)
|
83
|
+
indices.collect {|key| self[convert_key(key)]}
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param hash<Hash> The hash to merge with the mash.
|
87
|
+
#
|
88
|
+
# @return <Mash> A new mash with the hash values merged in.
|
89
|
+
def merge(hash)
|
90
|
+
self.dup.update(hash)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param key<Object>
|
94
|
+
# The key to delete from the mash.\
|
95
|
+
def delete(key)
|
96
|
+
super(convert_key(key))
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
|
100
|
+
#
|
101
|
+
# @return <Mash> A new mash without the selected keys.
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
105
|
+
# #=> { "two" => 2, "three" => 3 }
|
106
|
+
def except(*keys)
|
107
|
+
super(*keys.map {|k| convert_key(k)})
|
108
|
+
end
|
109
|
+
|
110
|
+
# Used to provide the same interface as Hash.
|
111
|
+
#
|
112
|
+
# @return <Mash> This mash unchanged.
|
113
|
+
def stringify_keys!; self end
|
114
|
+
|
115
|
+
# @return <Hash> The mash as a Hash with string keys.
|
116
|
+
def to_hash
|
117
|
+
Hash.new(default).merge(self)
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
# @param key<Object> The key to convert.
|
122
|
+
#
|
123
|
+
# @param <Object>
|
124
|
+
# The converted key. If the key was a symbol, it will be converted to a
|
125
|
+
# string.
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
def convert_key(key)
|
129
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param value<Object> The value to convert.
|
133
|
+
#
|
134
|
+
# @return <Object>
|
135
|
+
# The converted value. A Hash or an Array of hashes, will be converted to
|
136
|
+
# their Mash equivalents.
|
137
|
+
#
|
138
|
+
# @api private
|
139
|
+
def convert_value(value)
|
140
|
+
if value.class == Hash
|
141
|
+
value.to_mash
|
142
|
+
elsif value.is_a?(Array)
|
143
|
+
value.collect { |e| convert_value(e) }
|
144
|
+
else
|
145
|
+
value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rails
|
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 # Rails::Authentication
|
36
|
+
end # Rails
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class ActionController::Session::AbstractStore::SessionHash
|
2
|
+
|
3
|
+
# Access to the authentication object directly. Particularly useful
|
4
|
+
# for accessing the errors.
|
5
|
+
#
|
6
|
+
# === Example
|
7
|
+
#
|
8
|
+
# <%= error_messages_for session.authentication %>
|
9
|
+
#
|
10
|
+
def authentication
|
11
|
+
@authentication ||= Rails::Authentication.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Check to see if the current session is authenticated
|
15
|
+
# @return true if authenticated. false otherwise
|
16
|
+
def authenticated?
|
17
|
+
authentication.authenticated?
|
18
|
+
end
|
19
|
+
|
20
|
+
# Authenticates the session via the authentication object.
|
21
|
+
#
|
22
|
+
# See Rails::Authentication#authenticate for usage
|
23
|
+
def authenticate!(request, params, *rest)
|
24
|
+
authentication.authenticate!(request, params, *rest)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Provides access to the currently authenticated user.
|
28
|
+
def user
|
29
|
+
authentication.user
|
30
|
+
end
|
31
|
+
|
32
|
+
# set the currently authenticated user manually
|
33
|
+
# Rails::Authentication#store_user should know how to store the object into the session
|
34
|
+
def user=(the_user)
|
35
|
+
authentication.user = the_user
|
36
|
+
end
|
37
|
+
|
38
|
+
# Remove the user from the session and clear all data.
|
39
|
+
def abandon!
|
40
|
+
authentication.abandon!
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Rails::Authentication
|
2
|
+
module Strategies
|
3
|
+
# To use the password strategies, it is expected that you will provide
|
4
|
+
# an @authenticate@ method on your user class. This should take two parameters
|
5
|
+
# login, and password. It should return nil or the user object.
|
6
|
+
module Basic
|
7
|
+
|
8
|
+
class Base < Rails::Authentication::Strategy
|
9
|
+
abstract!
|
10
|
+
|
11
|
+
# Overwrite this method to customize the field
|
12
|
+
def self.password_param
|
13
|
+
:password
|
14
|
+
end
|
15
|
+
|
16
|
+
# Overwrite this method to customize the field
|
17
|
+
def self.login_param
|
18
|
+
:email
|
19
|
+
end
|
20
|
+
|
21
|
+
def password_param
|
22
|
+
@password_param ||= Base.password_param
|
23
|
+
end
|
24
|
+
|
25
|
+
def login_param
|
26
|
+
@login_param ||= Base.login_param
|
27
|
+
end
|
28
|
+
end # Base
|
29
|
+
end # Password
|
30
|
+
end # Strategies
|
31
|
+
end # Rails::Authentication
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'rails-auth/strategies/abstract_password'
|
2
|
+
# This strategy uses a login and password parameter.
|
3
|
+
#
|
4
|
+
# Overwrite the :password_param, and :login_param
|
5
|
+
# to return the name of the field (on the form) that you're using the
|
6
|
+
# login with. These can be strings or symbols
|
7
|
+
#
|
8
|
+
# == Required
|
9
|
+
#
|
10
|
+
# === Methods
|
11
|
+
# <User>.authenticate(login_param, password_param)
|
12
|
+
#
|
13
|
+
class Rails::Authentication
|
14
|
+
module Strategies
|
15
|
+
module Basic
|
16
|
+
class Form < Base
|
17
|
+
|
18
|
+
def run!
|
19
|
+
if request.params[login_param] && request.params[password_param]
|
20
|
+
user = user_class.authenticate(request.params[login_param], request.params[password_param])
|
21
|
+
if !user
|
22
|
+
request.session.authentication.errors.clear!
|
23
|
+
request.session.authentication.errors.add(login_param, strategy_error_message)
|
24
|
+
end
|
25
|
+
user
|
26
|
+
end
|
27
|
+
end # run!
|
28
|
+
|
29
|
+
def strategy_error_message
|
30
|
+
"#{login_param.to_s.capitalize} or #{password_param.to_s.capitalize} were incorrect"
|
31
|
+
end
|
32
|
+
|
33
|
+
end # Form
|
34
|
+
end # Password
|
35
|
+
end # Strategies
|
36
|
+
end # Authentication
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module Rails
|
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 [Rails::Authentiation::Strategy,Rails::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 Rails::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 Rails::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 Rails::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 < Rails::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
|
+
Rails::Authentication.strategies << klass
|
71
|
+
Rails::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 = Rails::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 = Rails::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, Rails::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 Rails::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
|
+
Rails::Authentication.user_class.constantize
|
202
|
+
end
|
203
|
+
|
204
|
+
end # Strategy
|
205
|
+
end # Rails::Authentication
|
206
|
+
end # Rails
|
data/lib/rails-auth.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class String
|
2
|
+
def /(other)
|
3
|
+
"#{self}/#{other}"
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rails-auth/mash'
|
8
|
+
|
9
|
+
require 'rails-auth/authenticated_helper'
|
10
|
+
require 'rails-auth/authentication'
|
11
|
+
require 'rails-auth/callbacks'
|
12
|
+
require 'rails-auth/errors'
|
13
|
+
require 'rails-auth/responses'
|
14
|
+
require 'rails-auth/session_mixin'
|
15
|
+
require 'rails-auth/strategy'
|
16
|
+
|
17
|
+
basic_path = "rails-auth/strategies"
|
18
|
+
|
19
|
+
# Rails::Authentication.register(:default_basic_auth, basic_path / "basic_auth.rb")
|
20
|
+
# Rails::Authentication.register(:default_openid, basic_path / "openid.rb")
|
21
|
+
Rails::Authentication.register(:default_password_form, basic_path / "password_form.rb")
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: myobie-rails-auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Herald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-10 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Authentication like merb, but for rails
|
17
|
+
email: myobie@mac.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.markdown
|
26
|
+
- VERSION.yml
|
27
|
+
- lib/rails-auth
|
28
|
+
- lib/rails-auth/authenticated_helper.rb
|
29
|
+
- lib/rails-auth/authentication.rb
|
30
|
+
- lib/rails-auth/callbacks.rb
|
31
|
+
- lib/rails-auth/errors.rb
|
32
|
+
- lib/rails-auth/helpers
|
33
|
+
- lib/rails-auth/helpers/all.rb
|
34
|
+
- lib/rails-auth/helpers/current_user.rb
|
35
|
+
- lib/rails-auth/helpers/logged_in.rb
|
36
|
+
- lib/rails-auth/helpers/redirect_back.rb
|
37
|
+
- lib/rails-auth/helpers/require_login_or.rb
|
38
|
+
- lib/rails-auth/mash.rb
|
39
|
+
- lib/rails-auth/responses.rb
|
40
|
+
- lib/rails-auth/session_mixin.rb
|
41
|
+
- lib/rails-auth/strategies
|
42
|
+
- lib/rails-auth/strategies/abstract_password.rb
|
43
|
+
- lib/rails-auth/strategies/password_form.rb
|
44
|
+
- lib/rails-auth/strategy.rb
|
45
|
+
- lib/rails-auth.rb
|
46
|
+
- test/rails-auth_test.rb
|
47
|
+
- test/test_helper.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/myobie/rails-auth
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --inline-source
|
53
|
+
- --charset=UTF-8
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.2.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 2
|
74
|
+
summary: Authentication like merb, but for rails
|
75
|
+
test_files: []
|
76
|
+
|