merbful_authentication 0.1.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.
Files changed (42) hide show
  1. data/LICENSE +25 -0
  2. data/README +69 -0
  3. data/Rakefile +35 -0
  4. data/TODO +5 -0
  5. data/activerecord_generators/merbful_authentication_model/merbful_authentication_model_generator.rb +65 -0
  6. data/activerecord_generators/merbful_authentication_model/templates/authenticated_system_orm_map.rb +34 -0
  7. data/activerecord_generators/merbful_authentication_model/templates/migration.rb +20 -0
  8. data/activerecord_generators/merbful_authentication_model/templates/model.rb +63 -0
  9. data/datamapper_generators/merbful_authentication_model/merbful_authentication_model_generator.rb +60 -0
  10. data/datamapper_generators/merbful_authentication_model/templates/authenticated_system_orm_map.rb +34 -0
  11. data/datamapper_generators/merbful_authentication_model/templates/model.rb +78 -0
  12. data/lib/merbful_authentication.rb +10 -0
  13. data/lib/merbful_authentication/merbtasks.rb +6 -0
  14. data/merb_generators/authentication/USAGE +5 -0
  15. data/merb_generators/authentication/authentication_generator.rb +256 -0
  16. data/merb_generators/authentication/templates/activation.html.erb +1 -0
  17. data/merb_generators/authentication/templates/activation.text.erb +1 -0
  18. data/merb_generators/authentication/templates/authenticated_system_controller.rb +132 -0
  19. data/merb_generators/authentication/templates/authenticated_system_model.rb +97 -0
  20. data/merb_generators/authentication/templates/login.html.erb +14 -0
  21. data/merb_generators/authentication/templates/mail_controller.rb +13 -0
  22. data/merb_generators/authentication/templates/model_controller.rb +33 -0
  23. data/merb_generators/authentication/templates/new_model.html.erb +18 -0
  24. data/merb_generators/authentication/templates/session_controller.rb +33 -0
  25. data/merb_generators/authentication/templates/signup.html.erb +8 -0
  26. data/merb_generators/authentication/templates/signup.text.erb +8 -0
  27. data/rspec_generators/merbful_authentication_tests/merbful_authentication_tests_generator.rb +83 -0
  28. data/rspec_generators/merbful_authentication_tests/templates/authenticated_system_spec_helper.rb +22 -0
  29. data/rspec_generators/merbful_authentication_tests/templates/model_controller_spec.rb +78 -0
  30. data/rspec_generators/merbful_authentication_tests/templates/model_spec.rb +357 -0
  31. data/rspec_generators/merbful_authentication_tests/templates/model_spec_helper.rb +8 -0
  32. data/rspec_generators/merbful_authentication_tests/templates/session_controller_spec.rb +101 -0
  33. data/rspec_generators/merbful_authentication_tests/templates/user_mailer_spec.rb +70 -0
  34. data/test_unit_generators/merbful_authentication_tests/USAGE +5 -0
  35. data/test_unit_generators/merbful_authentication_tests/merbful_authentication_tests_generator.rb +84 -0
  36. data/test_unit_generators/merbful_authentication_tests/templates/authenticated_system_test_helper.rb +50 -0
  37. data/test_unit_generators/merbful_authentication_tests/templates/functional_test.rb +92 -0
  38. data/test_unit_generators/merbful_authentication_tests/templates/mailer_test.rb +66 -0
  39. data/test_unit_generators/merbful_authentication_tests/templates/model_functional_test.rb +92 -0
  40. data/test_unit_generators/merbful_authentication_tests/templates/model_test_helper.rb +8 -0
  41. data/test_unit_generators/merbful_authentication_tests/templates/unit_test.rb +142 -0
  42. metadata +114 -0
@@ -0,0 +1,10 @@
1
+ # make sure we're running inside Merb
2
+ if defined?(Merb::Plugins)
3
+
4
+ # Merb gives you a Merb::Plugins.config hash...feel free to put your stuff in your piece of it
5
+ Merb::Plugins.config[:merbful_authentication] = {
6
+ :chickens => false
7
+ }
8
+
9
+ Merb::Plugins.add_rakefiles "merbful_authentication/merbtasks"
10
+ end
@@ -0,0 +1,6 @@
1
+ namespace :merbful_authentication do
2
+ desc "Do something for merbful_authentication"
3
+ task :default do
4
+ puts "merbful_authentication doesn't do anything"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+
3
+
4
+ Usage:
5
+
@@ -0,0 +1,256 @@
1
+ require 'merb'
2
+ class AuthenticationGenerator < RubiGen::Base
3
+
4
+ default_options :author => nil
5
+
6
+ attr_reader :name,
7
+ :class_name,
8
+ :class_path,
9
+ :file_name,
10
+ :class_nesting,
11
+ :class_nesting_depth,
12
+ :plural_name,
13
+ :singular_name,
14
+ :controller_name,
15
+ :controller_class_path,
16
+ :controller_file_path,
17
+ :controller_class_nesting,
18
+ :controller_class_nesting_depth,
19
+ :controller_class_name,
20
+ :controller_singular_name,
21
+ :controller_plural_name
22
+ alias_method :controller_file_name, :controller_singular_name
23
+ alias_method :controller_table_name, :controller_plural_name
24
+ attr_reader :model_controller_name,
25
+ :model_controller_class_path,
26
+ :model_controller_file_path,
27
+ :model_controller_class_nesting,
28
+ :model_controller_class_nesting_depth,
29
+ :model_controller_class_name,
30
+ :model_controller_singular_name,
31
+ :model_controller_plural_name
32
+ alias_method :model_controller_file_name, :model_controller_singular_name
33
+ alias_method :model_controller_table_name, :model_controller_plural_name
34
+ attr_reader :include_activation
35
+
36
+ def initialize(runtime_args, runtime_options = {})
37
+ usage if runtime_args.empty?
38
+ super
39
+ extract_options
40
+ assign_names!(runtime_args.shift)
41
+ @include_activation = options[:include_activation]
42
+
43
+ @controller_name = runtime_args.shift || 'sessions'
44
+ @model_controller_name = @name.pluralize
45
+ @mailer_controller_name = @name
46
+
47
+ # sessions controller
48
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
49
+ @controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
50
+
51
+ if @controller_class_nesting.empty?
52
+ @controller_class_name = @controller_class_name_without_nesting
53
+ else
54
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
55
+ end
56
+
57
+ # model controller
58
+ base_name, @model_controller_class_path, @model_controller_file_path, @model_controller_class_nesting, @model_controller_class_nesting_depth = extract_modules(@model_controller_name)
59
+ @model_controller_class_name_without_nesting, @model_controller_singular_name, @model_controller_plural_name = inflect_names(base_name)
60
+
61
+ if @model_controller_class_nesting.empty?
62
+ @model_controller_class_name = @model_controller_class_name_without_nesting
63
+ else
64
+ @model_controller_class_name = "#{@model_controller_class_nesting}::#{@model_controller_class_name_without_nesting}"
65
+ end
66
+ end
67
+
68
+ def manifest
69
+ manifest_result = record do |m|
70
+ # Check for class naming collisions.
71
+ m.class_collisions controller_class_path, "#{controller_class_name}", # Sessions Controller
72
+ "Merb::#{controller_class_name}Helper"
73
+ m.class_collisions model_controller_class_path, "#{model_controller_class_name}", # Model Controller
74
+ "Merb::#{model_controller_class_name}Helper"
75
+ m.class_collisions class_path, "#{class_name}", "#{class_name}Mailer"# , "#{class_name}MailerTest", "#{class_name}Observer"
76
+ m.class_collisions [], 'AuthenticatedSystem::Controller', 'AuthenticatedSystem::Model'
77
+
78
+ # Controller, helper, views, and test directories.
79
+
80
+ m.directory File.join('app/controllers', controller_class_path)
81
+ m.directory File.join('app/controllers', model_controller_class_path)
82
+
83
+ m.directory File.join('app/helpers', controller_class_path)
84
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
85
+
86
+ m.directory File.join('app/controllers', model_controller_class_path)
87
+ m.directory File.join('app/helpers', model_controller_class_path)
88
+ m.directory File.join('app/views', model_controller_class_path, model_controller_file_name)
89
+
90
+ # Generate the authenticated system" libraries
91
+ m.directory "lib"
92
+ m.template "authenticated_system_controller.rb", "lib/authenticated_system_controller.rb"
93
+ m.template "authenticated_system_model.rb", "lib/authenticated_system_model.rb"
94
+
95
+ # Mailer directory for activation
96
+ if options[:include_activation]
97
+ m.directory File.join('app/mailers/views', "#{singular_name}_mailer")
98
+ m.template "mail_controller.rb", File.join('app/mailers',
99
+ "#{singular_name}_mailer.rb")
100
+ [:html, :text].each do |format|
101
+ [:signup, :activation].each do |action|
102
+ m.template "#{action}.#{format}.erb", File.join('app/mailers/views',
103
+ "#{singular_name}_mailer",
104
+ "#{action}_notification.#{format}.erb")
105
+ end
106
+ end
107
+ end
108
+
109
+ # Generate the model
110
+ model_attributes = {
111
+ :class_name => class_name,
112
+ :class_path => class_path,
113
+ :file_name => file_name,
114
+ :class_nesting => class_nesting,
115
+ :class_nesting_depth => class_nesting_depth,
116
+ :plural_name => plural_name,
117
+ :singular_name => singular_name,
118
+ :include_activation => options[:include_activation]
119
+ }
120
+ m.dependency "merbful_authentication_model", [name], model_attributes
121
+
122
+ # Generate the sessions controller
123
+ m.template "session_controller.rb", File.join('app/controllers',
124
+ controller_class_path,
125
+ "#{controller_file_name}.rb")
126
+
127
+ # Generate the model controller
128
+ m.template "model_controller.rb", File.join('app/controllers',
129
+ model_controller_class_path,
130
+ "#{model_controller_file_name}.rb")
131
+
132
+ # Controller templates
133
+ m.template 'login.html.erb', File.join('app/views', controller_class_path, controller_file_name, "new.html.erb")
134
+ m.template 'new_model.html.erb', File.join('app/views', model_controller_class_path, model_controller_file_name, "new.html.erb")
135
+
136
+
137
+ controller_attributes = {
138
+ :controller_name => controller_name,
139
+ :controller_class_path => controller_class_path,
140
+ :controller_file_path => controller_file_path,
141
+ :controller_class_nesting => controller_class_nesting,
142
+ :controller_class_nesting_depth => controller_class_nesting_depth,
143
+ :controller_class_name => controller_class_name,
144
+ :controller_singular_name => controller_singular_name,
145
+ :controller_plural_name => controller_plural_name,
146
+ :model_controller_name => model_controller_name,
147
+ :model_controller_class_path => model_controller_class_path,
148
+ :model_controller_file_path => model_controller_file_path,
149
+ :model_controller_class_nesting => model_controller_class_nesting,
150
+ :model_controller_class_nesting_depth => model_controller_class_nesting_depth,
151
+ :model_controller_class_name => model_controller_class_name,
152
+ :model_controller_singular_name => model_controller_singular_name,
153
+ :model_controller_plural_name => model_controller_plural_name,
154
+ :include_activation => options[:include_activation]
155
+ }
156
+ # Generate the tests
157
+ m.dependency "merbful_authentication_tests", [name], model_attributes.dup.merge!(controller_attributes)
158
+ end
159
+
160
+ action = nil
161
+ action = $0.split("/")[1]
162
+ case action
163
+ when 'generate'
164
+ puts finishing_message
165
+ when 'destroy'
166
+ puts "Thanx for using merbful_authentication"
167
+ end
168
+
169
+ manifest_result
170
+
171
+ end
172
+
173
+ protected
174
+ # Override with your own usage banner.
175
+ def banner
176
+ out = <<-EOD;
177
+ Usage: #{$0} authenticated ModelName [ControllerName]
178
+ EOD
179
+ end
180
+
181
+ def add_options!(opt)
182
+ opt.separator ''
183
+ opt.separator 'Options:'
184
+ opt.on("--skip-migration",
185
+ "Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
186
+ opt.on("--include-activation",
187
+ "Generate signup 'activation code' confirmation via email") { |v| options[:include_activation] = true }
188
+ end
189
+
190
+ def extract_options
191
+ # for each option, extract it into a local variable (and create an "attr_reader :author" at the top)
192
+ # Templates can access these value via the attr_reader-generated methods, but not the
193
+ # raw instance variable value.
194
+ # @author = options[:author]
195
+ end
196
+
197
+
198
+ # Borrowed from RailsGenerators
199
+ # Extract modules from filesystem-style or ruby-style path:
200
+ # good/fun/stuff
201
+ # Good::Fun::Stuff
202
+ # produce the same results.
203
+ def extract_modules(name)
204
+ modules = name.include?('/') ? name.split('/') : name.split('::')
205
+ name = modules.pop
206
+ path = modules.map { |m| m.underscore }
207
+ file_path = (path + [name.underscore]).join('/')
208
+ nesting = modules.map { |m| m.camelize }.join('::')
209
+ [name, path, file_path, nesting, modules.size]
210
+ end
211
+
212
+ def inflect_names(name)
213
+ camel = name.camelize
214
+ under = camel.underscore
215
+ plural = under.pluralize
216
+ [camel, under, plural]
217
+ end
218
+
219
+ def assign_names!(name)
220
+ @name = name
221
+ base_name, @class_path, @file_name, @class_nesting, @class_nesting_depth = extract_modules(@name)
222
+ @class_name_without_nesting, @singular_name, @plural_name = inflect_names(base_name)
223
+ @table_name = @name.pluralize
224
+ # @table_name = (!defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names) ? plural_name : singular_name
225
+ @table_name.gsub! '/', '_'
226
+ if @class_nesting.empty?
227
+ @class_name = @class_name_without_nesting
228
+ else
229
+ @table_name = @class_nesting.underscore << "_" << @table_name
230
+ @class_name = "#{@class_nesting}::#{@class_name_without_nesting}"
231
+ end
232
+ end
233
+
234
+ private
235
+
236
+ def finishing_message
237
+ output = <<-EOD
238
+ #{"-" * 70}
239
+ Don't forget to:
240
+
241
+ - add named routes for authentication. These are currently required
242
+ In config/router.rb
243
+
244
+ r.resources :#{plural_name}
245
+ r.match("/login").to(:controller => "#{controller_class_name}", :action => "create").name(:login)
246
+ r.match("/logout").to(:controller => "#{controller_class_name}", :action => "destroy").name(:logout)
247
+ EOD
248
+ if options[:include_activation]
249
+ output << " r.match(\"/#{plural_name}/activate/:activation_code\").to(:controller => \"#{model_controller_class_name}\", :action => \"activate\").name(:#{singular_name}_activation)"
250
+ end
251
+
252
+ output << "\n\n" << ("-" * 70)
253
+ end
254
+
255
+
256
+ end
@@ -0,0 +1 @@
1
+ Your email has been authenticated <%%= @<%= singular_name %>.login %>
@@ -0,0 +1 @@
1
+ Your email has been authenticated <%%= @<%= singular_name %>.login %>
@@ -0,0 +1,132 @@
1
+ module AuthenticatedSystem
2
+ module Controller
3
+ protected
4
+ # Returns true or false if the <%= singular_name %> is logged in.
5
+ # Preloads @current_<%= singular_name %> with the <%= singular_name %> model if they're logged in.
6
+ def logged_in?
7
+ current_<%= singular_name %> != :false
8
+ end
9
+
10
+ # Accesses the current <%= singular_name %> from the session. Set it to :false if login fails
11
+ # so that future calls do not hit the database.
12
+ def current_<%= singular_name %>
13
+ @current_<%= singular_name %> ||= (login_from_session || login_from_basic_auth || login_from_cookie || :false)
14
+ end
15
+
16
+ # Store the given <%= singular_name %> in the session.
17
+ def current_<%= singular_name %>=(new_<%= singular_name %>)
18
+ session[:<%= singular_name %>] = (new_<%= singular_name %>.nil? || new_<%= singular_name %>.is_a?(Symbol)) ? nil : new_<%= singular_name %>.id
19
+ @current_<%= singular_name %> = new_<%= singular_name %>
20
+ end
21
+
22
+ # Check if the <%= singular_name %> is authorized
23
+ #
24
+ # Override this method in your controllers if you want to restrict access
25
+ # to only a few actions or if you want to check if the <%= singular_name %>
26
+ # has the correct rights.
27
+ #
28
+ # Example:
29
+ #
30
+ # # only allow nonbobs
31
+ # def authorized?
32
+ # current_<%= singular_name %>.login != "bob"
33
+ # end
34
+ def authorized?
35
+ logged_in?
36
+ end
37
+
38
+ # Filter method to enforce a login requirement.
39
+ #
40
+ # To require logins for all actions, use this in your controllers:
41
+ #
42
+ # before_filter :login_required
43
+ #
44
+ # To require logins for specific actions, use this in your controllers:
45
+ #
46
+ # before_filter :login_required, :only => [ :edit, :update ]
47
+ #
48
+ # To skip this in a subclassed controller:
49
+ #
50
+ # skip_before_filter :login_required
51
+ #
52
+ def login_required
53
+ authorized? || throw(:halt, :access_denied)
54
+ end
55
+
56
+ # Redirect as appropriate when an access request fails.
57
+ #
58
+ # The default action is to redirect to the login screen.
59
+ #
60
+ # Override this method in your controllers if you want to have special
61
+ # behavior in case the <%= singular_name %> is not authorized
62
+ # to access the requested action. For example, a popup window might
63
+ # simply close itself.
64
+ def access_denied
65
+ case content_type
66
+ when :html
67
+ store_location
68
+ redirect url(:login)
69
+ when :xml
70
+ headers["Status"] = "Unauthorized"
71
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
72
+ set_status(401)
73
+ render :text => "Couldn't authenticate you"
74
+ end
75
+ end
76
+
77
+ # Store the URI of the current request in the session.
78
+ #
79
+ # We can return to this location by calling #redirect_back_or_default.
80
+ def store_location
81
+ session[:return_to] = request.uri
82
+ end
83
+
84
+ # Redirect to the URI stored by the most recent store_location call or
85
+ # to the passed default.
86
+ def redirect_back_or_default(default)
87
+ redirect(session[:return_to] || default)
88
+ session[:return_to] = nil
89
+ end
90
+
91
+ # Inclusion hook to make #current_<%= singular_name %> and #logged_in?
92
+ # available as ActionView helper methods.
93
+ # def self.included(base)
94
+ # base.send :helper_method, :current_<%= singular_name %>, :logged_in?
95
+ # end
96
+
97
+ # Called from #current_<%= singular_name %>. First attempt to login by the <%= singular_name %> id stored in the session.
98
+ def login_from_session
99
+ self.current_<%= singular_name %> = <%= class_name %>.find_authenticated_model_with_id(session[:<%= singular_name %>]) if session[:<%= singular_name %>]
100
+ end
101
+
102
+ # Called from #current_<%= singular_name %>. Now, attempt to login by basic authentication information.
103
+ def login_from_basic_auth
104
+ <%= singular_name %>name, passwd = get_auth_data
105
+ self.current_<%= singular_name %> = <%= class_name %>.authenticate(<%= singular_name %>name, passwd) if <%= singular_name %>name && passwd
106
+ end
107
+
108
+ # Called from #current_<%= singular_name %>. Finaly, attempt to login by an expiring token in the cookie.
109
+ def login_from_cookie
110
+ <%= singular_name %> = cookies[:auth_token] && <%= class_name %>.find_authenticated_model_with_remember_token(cookies[:auth_token])
111
+ if <%= singular_name %> && <%= singular_name %>.remember_token?
112
+ <%= singular_name %>.remember_me
113
+ cookies[:auth_token] = { :value => <%= singular_name %>.remember_token, :expires => <%= singular_name %>.remember_token_expires_at }
114
+ self.current_<%= singular_name %> = <%= singular_name %>
115
+ end
116
+ end
117
+
118
+ def reset_session
119
+ session.data.each{|k,v| session.data.delete(k)}
120
+ end
121
+
122
+ private
123
+ @@http_auth_headers = %w(Authorization HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION)
124
+
125
+ # gets BASIC auth info
126
+ def get_auth_data
127
+ auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
128
+ auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
129
+ return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,97 @@
1
+ require 'authenticated_system_orm_map'
2
+ module AuthenticatedSystem
3
+ module Model
4
+
5
+ def self.included(base)
6
+ base.send(:include, InstanceMethods)
7
+ base.send(:extend, ClassMethods)
8
+ base.send(:extend, AuthenticatedSystem::OrmMap )
9
+ end
10
+
11
+ module InstanceMethods
12
+ def authenticated?(password)
13
+ crypted_password == encrypt(password)
14
+ end
15
+
16
+ # before filter
17
+ def encrypt_password
18
+ return if password.blank?
19
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
20
+ self.crypted_password = encrypt(password)
21
+ end
22
+
23
+ # Encrypts the password with the <%= singular_name %> salt
24
+ def encrypt(password)
25
+ self.class.encrypt(password, salt)
26
+ end
27
+
28
+ def remember_token?
29
+ remember_token_expires_at && DateTime.now < DateTime.parse(remember_token_expires_at.to_s)
30
+ end
31
+
32
+ def remember_me_until(time)
33
+ self.remember_token_expires_at = time
34
+ self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
35
+ save
36
+ end
37
+
38
+ def remember_me_for(time)
39
+ remember_me_until (Time.now + time)
40
+ end
41
+
42
+ # These create and unset the fields required for remembering <%= singular_name %>s between browser closes
43
+ # Default of 2 weeks
44
+ def remember_me
45
+ remember_me_for (Merb::Const::WEEK * 2)
46
+ end
47
+
48
+ def forget_me
49
+ self.remember_token_expires_at = nil
50
+ self.remember_token = nil
51
+ self.save
52
+ end
53
+ <% if include_activation -%>
54
+ # Returns true if the <%= singular_name %> has just been activated.
55
+ def recently_activated?
56
+ @activated
57
+ end
58
+
59
+ def activated?
60
+ return false if self.new_record?
61
+ !! activation_code.nil?
62
+ end
63
+
64
+ def active?
65
+ # the existence of an activation code means they have not activated yet
66
+ activation_code.nil?
67
+ end
68
+ <% end %>
69
+ protected
70
+ <% if include_activation -%>
71
+ def make_activation_code
72
+ self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
73
+ end
74
+ <% end -%>
75
+
76
+ def password_required?
77
+ crypted_password.blank? || !password.blank?
78
+ end
79
+
80
+ end
81
+
82
+ module ClassMethods
83
+ # Encrypts some data with the salt.
84
+ def encrypt(password, salt)
85
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
86
+ end
87
+
88
+ # Authenticates a <%= singular_name %> by their login name and unencrypted password. Returns the <%= singular_name %> or nil.
89
+ def authenticate(login, password)
90
+ u = find_activated_authenticated_model_with_login(login) # need to get the salt
91
+ u && u.authenticated?(password) ? u : nil
92
+ end
93
+ end
94
+
95
+
96
+ end
97
+ end