myobie-rails-auth 0.0.0
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/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
|
+
|