merbful_authentication 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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