pager-restful_open_id_authentication 1.0.20080507

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 (29) hide show
  1. data/README +63 -0
  2. data/Rakefile +22 -0
  3. data/generators/open_id_authenticated/USAGE +1 -0
  4. data/generators/open_id_authenticated/open_id_authenticated_generator.rb +215 -0
  5. data/generators/open_id_authenticated/templates/activation.rhtml +3 -0
  6. data/generators/open_id_authenticated/templates/authenticated_system.rb +120 -0
  7. data/generators/open_id_authenticated/templates/authenticated_test_helper.rb +113 -0
  8. data/generators/open_id_authenticated/templates/controller.rb +94 -0
  9. data/generators/open_id_authenticated/templates/fixtures.yml +17 -0
  10. data/generators/open_id_authenticated/templates/functional_test.rb +85 -0
  11. data/generators/open_id_authenticated/templates/helper.rb +2 -0
  12. data/generators/open_id_authenticated/templates/login.rhtml +20 -0
  13. data/generators/open_id_authenticated/templates/migration.rb +45 -0
  14. data/generators/open_id_authenticated/templates/model.rb +93 -0
  15. data/generators/open_id_authenticated/templates/model_controller.rb +30 -0
  16. data/generators/open_id_authenticated/templates/model_functional_test.rb +72 -0
  17. data/generators/open_id_authenticated/templates/model_helper.rb +2 -0
  18. data/generators/open_id_authenticated/templates/notifier.rb +25 -0
  19. data/generators/open_id_authenticated/templates/notifier_test.rb +31 -0
  20. data/generators/open_id_authenticated/templates/observer.rb +11 -0
  21. data/generators/open_id_authenticated/templates/open_id_form.rhtml +14 -0
  22. data/generators/open_id_authenticated/templates/signup.rhtml +22 -0
  23. data/generators/open_id_authenticated/templates/signup_notification.rhtml +8 -0
  24. data/generators/open_id_authenticated/templates/unit_test.rb +101 -0
  25. data/install.rb +1 -0
  26. data/lib/controller_methods.rb +137 -0
  27. data/lib/open_id_store.rb +69 -0
  28. data/rails/init.rb +15 -0
  29. metadata +81 -0
data/README ADDED
@@ -0,0 +1,63 @@
1
+ RESTful OpenID Authentication 0.1
2
+ ===================
3
+
4
+ A Ruby on Rails plugin for OpenID authentication and profile exchange
5
+
6
+ Developed by EastMedia (http://www.eastmedia.com) for VeriSign
7
+ Contact Matt Pelletier - matt AT eastmedia.com
8
+
9
+ Thanks to atmos for working on an extension of restful_authentication that piggy-backed
10
+ the original open_id_consumer plugin. This plugin merges some of those ideas with others
11
+ and merged them with the open_id_consumer plugin.
12
+
13
+
14
+ WHAT IS IT?
15
+ -----------
16
+
17
+ Installing this plugin allows you to extend your Rails application with OpenID authentication and profile exchange.
18
+
19
+
20
+ PRE-REQUISITES
21
+ --------------
22
+
23
+ * JanRain's Yadis and OpenID 1.1 libraries in Ruby.
24
+ * These can be obtained using 'gem install ruby-openid'
25
+ * Requires you have an OpenID account, such as from one of the following:
26
+ * http://pip.verisignlabs.com
27
+ * http://www.myopenid.com
28
+ * Rails 1.2.2 (for the singular resource routes. otherwise 1.2.1 should be fine)
29
+ * SimplyRestful plugin from Rails
30
+ * ./script/plugin install http://svn.rubonrails.org/rails/plugins/simply_restful
31
+
32
+
33
+ INSTALLATION
34
+ ------------
35
+
36
+ To install you need to run the generator script. This script will:
37
+ * Create a migration to create tables for the user and OpenID-related tables
38
+ * Generate two controllers (one for the model and one for the session)
39
+ * Generate the user model
40
+
41
+ ./script/generate open_id_authenticated USERMODEL CONTROLLERNAME
42
+
43
+ Where USERMODEL would be 'User' and CONTROLLERNAME would be 'Session' by default
44
+
45
+ You should add RESTful routes (this is also recommended in the generator output).
46
+ For example, if your model is 'User' and your login controller is 'Session':
47
+
48
+ map.resources :users
49
+ map.resource :session, :collection => { :begin => :post, :complete => :get }
50
+
51
+
52
+ EDUCATION
53
+ ---------
54
+
55
+ On the Web:
56
+ http://www.openidenabled.com/
57
+ http://www.openidenabled.com/openid/simple-registration-extension/
58
+
59
+ On IRC:
60
+ Freenode: #openid and ##identity
61
+
62
+ More to come
63
+
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the restful_open_id_authentication plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the restful_open_id_authentication plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'RestfulOpenIDAuthentication'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1 @@
1
+ ./script/generate open_id_authenticated USERMODEL CONTROLLERNAME
@@ -0,0 +1,215 @@
1
+ class OpenIdAuthenticatedGenerator < Rails::Generator::NamedBase
2
+ attr_reader :controller_name,
3
+ :controller_class_path,
4
+ :controller_file_path,
5
+ :controller_class_nesting,
6
+ :controller_class_nesting_depth,
7
+ :controller_class_name,
8
+ :controller_singular_name,
9
+ :controller_plural_name
10
+ alias_method :controller_file_name, :controller_singular_name
11
+ alias_method :controller_table_name, :controller_plural_name
12
+ attr_reader :model_controller_name,
13
+ :model_controller_class_path,
14
+ :model_controller_file_path,
15
+ :model_controller_class_nesting,
16
+ :model_controller_class_nesting_depth,
17
+ :model_controller_class_name,
18
+ :model_controller_singular_name,
19
+ :model_controller_plural_name
20
+ alias_method :model_controller_file_name, :model_controller_singular_name
21
+ alias_method :model_controller_table_name, :model_controller_plural_name
22
+
23
+ def initialize(runtime_args, runtime_options = {})
24
+ super
25
+
26
+ @controller_name = args.shift || 'session'
27
+ @model_controller_name = @name.pluralize
28
+
29
+ # sessions controller
30
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
31
+ @controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
32
+
33
+ if @controller_class_nesting.empty?
34
+ @controller_class_name = @controller_class_name_without_nesting
35
+ else
36
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
37
+ end
38
+
39
+ # model controller
40
+ base_name, @model_controller_class_path, @model_controller_file_path, @model_controller_class_nesting, @model_controller_class_nesting_depth = extract_modules(@model_controller_name)
41
+ @model_controller_class_name_without_nesting, @model_controller_singular_name, @model_controller_plural_name = inflect_names(base_name)
42
+
43
+ if @model_controller_class_nesting.empty?
44
+ @model_controller_class_name = @model_controller_class_name_without_nesting
45
+ else
46
+ @model_controller_class_name = "#{@model_controller_class_nesting}::#{@model_controller_class_name_without_nesting}"
47
+ end
48
+ end
49
+
50
+ def manifest
51
+ recorded_session = record do |m|
52
+ # Check for class naming collisions.
53
+ m.class_collisions controller_class_path, "#{controller_class_name}Controller", # Sessions Controller
54
+ "#{controller_class_name}Helper"
55
+ m.class_collisions model_controller_class_path, "#{model_controller_class_name}Controller", # Model Controller
56
+ "#{model_controller_class_name}Helper"
57
+ m.class_collisions class_path, "#{class_name}", "#{class_name}Notifier", "#{class_name}NotifierTest", "#{class_name}Observer"
58
+ m.class_collisions [], 'AuthenticatedSystem', 'AuthenticatedTestHelper'
59
+
60
+ # Controller, helper, views, and test directories.
61
+ m.directory File.join('app/models', class_path)
62
+ m.directory File.join('app/controllers', controller_class_path)
63
+ m.directory File.join('app/controllers', model_controller_class_path)
64
+ m.directory File.join('app/helpers', controller_class_path)
65
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
66
+ m.directory File.join('app/views', class_path, "#{file_name}_notifier")
67
+ m.directory File.join('test/functional', controller_class_path)
68
+ m.directory File.join('app/controllers', model_controller_class_path)
69
+ m.directory File.join('app/helpers', model_controller_class_path)
70
+ m.directory File.join('app/views', model_controller_class_path, model_controller_file_name)
71
+ m.directory File.join('test/functional', model_controller_class_path)
72
+ m.directory File.join('test/unit', class_path)
73
+
74
+ m.template 'model.rb',
75
+ File.join('app/models',
76
+ class_path,
77
+ "#{file_name}.rb")
78
+
79
+ if options[:include_activation]
80
+ %w( notifier observer ).each do |model_type|
81
+ m.template "#{model_type}.rb", File.join('app/models',
82
+ class_path,
83
+ "#{file_name}_#{model_type}.rb")
84
+ end
85
+ end
86
+
87
+ m.template 'controller.rb',
88
+ File.join('app/controllers',
89
+ controller_class_path,
90
+ "#{controller_file_name}_controller.rb")
91
+
92
+ m.template 'model_controller.rb',
93
+ File.join('app/controllers',
94
+ model_controller_class_path,
95
+ "#{model_controller_file_name}_controller.rb")
96
+
97
+ m.template 'authenticated_system.rb',
98
+ File.join('lib', 'authenticated_system.rb')
99
+
100
+ m.template 'authenticated_test_helper.rb',
101
+ File.join('lib', 'authenticated_test_helper.rb')
102
+
103
+ m.template 'functional_test.rb',
104
+ File.join('test/functional',
105
+ controller_class_path,
106
+ "#{controller_file_name}_controller_test.rb")
107
+
108
+ m.template 'model_functional_test.rb',
109
+ File.join('test/functional',
110
+ model_controller_class_path,
111
+ "#{model_controller_file_name}_controller_test.rb")
112
+
113
+ m.template 'helper.rb',
114
+ File.join('app/helpers',
115
+ controller_class_path,
116
+ "#{controller_file_name}_helper.rb")
117
+
118
+ m.template 'model_helper.rb',
119
+ File.join('app/helpers',
120
+ model_controller_class_path,
121
+ "#{model_controller_file_name}_helper.rb")
122
+
123
+ m.template 'unit_test.rb',
124
+ File.join('test/unit',
125
+ class_path,
126
+ "#{file_name}_test.rb")
127
+
128
+ if options[:include_activation]
129
+ m.template 'notifier_test.rb', File.join('test/unit', class_path, "#{file_name}_notifier_test.rb")
130
+ end
131
+
132
+ m.template 'fixtures.yml',
133
+ File.join('test/fixtures',
134
+ "#{table_name}.yml")
135
+
136
+ # Controller templates
137
+ m.template 'login.rhtml', File.join('app/views', controller_class_path, controller_file_name, "new.rhtml")
138
+ m.template 'open_id_form.rhtml', File.join('app/views', controller_class_path, controller_file_name, "_open_id_form.rhtml")
139
+ m.template 'signup.rhtml', File.join('app/views', model_controller_class_path, model_controller_file_name, "new.rhtml")
140
+
141
+ if options[:include_activation]
142
+ # Mailer templates
143
+ %w( activation signup_notification ).each do |action|
144
+ m.template "#{action}.rhtml",
145
+ File.join('app/views', "#{file_name}_notifier", "#{action}.rhtml")
146
+ end
147
+ end
148
+
149
+ unless options[:skip_migration]
150
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => {
151
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
152
+ }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
153
+ end
154
+ end
155
+
156
+ action = nil
157
+ action = $0.split("/")[1]
158
+ case action
159
+ when "generate"
160
+ puts
161
+ puts ("-" * 70)
162
+ puts "Don't forget to:"
163
+ puts
164
+ puts " - add restful routes in config/routes.rb"
165
+ puts " map.resources :#{model_controller_file_name}"
166
+ puts " map.resource :#{controller_file_name}, :collection => { :begin => :post, :complete => :get }"
167
+ if options[:include_activation]
168
+ puts " map.activate '/activate/:activation_code', :controller => '#{model_controller_file_name}', :action => 'activate'"
169
+ puts
170
+ puts " - add an observer to config/environment.rb"
171
+ puts " config.active_record.observers = :#{file_name}_observer"
172
+ end
173
+ puts
174
+ puts "Try these for some familiar login URLs if you like:"
175
+ puts
176
+ puts " map.signup '/signup', :controller => '#{model_controller_file_name}', :action => 'new'"
177
+ puts " map.login '/login', :controller => '#{controller_file_name}', :action => 'new'"
178
+ puts " map.logout '/logout', :controller => '#{controller_file_name}', :action => 'destroy'"
179
+ puts
180
+ puts ("-" * 70)
181
+ puts
182
+ when "destroy"
183
+ puts
184
+ puts ("-" * 70)
185
+ puts
186
+ puts "Thanks for using restful_open_id_authentication"
187
+ puts
188
+ puts "Don't forget to comment out the observer line in environment.rb"
189
+ puts " (This was optional so it may not even be there)"
190
+ puts " # config.active_record.observers = :#{file_name}_observer"
191
+ puts
192
+ puts ("-" * 70)
193
+ puts
194
+ else
195
+ puts
196
+ end
197
+
198
+ recorded_session
199
+ end
200
+
201
+ protected
202
+ # Override with your own usage banner.
203
+ def banner
204
+ "Usage: #{$0} authenticated ModelName [ControllerName]"
205
+ end
206
+
207
+ def add_options!(opt)
208
+ opt.separator ''
209
+ opt.separator 'Options:'
210
+ opt.on("--skip-migration",
211
+ "Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
212
+ opt.on("--include-activation",
213
+ "Generate signup 'activation code' confirmation via email") { |v| options[:include_activation] = v }
214
+ end
215
+ end
@@ -0,0 +1,3 @@
1
+ <%%= @<%= file_name %>.login %>, your account has been activated. You may now start adding your plugins:
2
+
3
+ <%%= @url %>
@@ -0,0 +1,120 @@
1
+ module AuthenticatedSystem
2
+ protected
3
+ # Returns true or false if the user is logged in.
4
+ # Preloads @current_<%= file_name %> with the user model if they're logged in.
5
+ def logged_in?
6
+ current_<%= file_name %> != :false
7
+ end
8
+
9
+ # Accesses the current <%= file_name %> from the session.
10
+ def current_<%= file_name %>
11
+ @current_<%= file_name %> ||= (session[:<%= file_name %>] && <%= class_name %>.find_by_id(session[:<%= file_name %>])) || :false
12
+ end
13
+
14
+ # Store the given <%= file_name %> in the session.
15
+ def current_<%= file_name %>=(new_<%= file_name %>)
16
+ session[:<%= file_name %>] = (new_<%= file_name %>.nil? || new_<%= file_name %>.is_a?(Symbol)) ? nil : new_<%= file_name %>.id
17
+ @current_<%= file_name %> = new_<%= file_name %>
18
+ end
19
+
20
+ # Check if the <%= file_name %> is authorized.
21
+ #
22
+ # Override this method in your controllers if you want to restrict access
23
+ # to only a few actions or if you want to check if the <%= file_name %>
24
+ # has the correct rights.
25
+ #
26
+ # Example:
27
+ #
28
+ # # only allow nonbobs
29
+ # def authorize?
30
+ # current_<%= file_name %>.login != "bob"
31
+ # end
32
+ def authorized?
33
+ true
34
+ end
35
+
36
+ # Filter method to enforce a login requirement.
37
+ #
38
+ # To require logins for all actions, use this in your controllers:
39
+ #
40
+ # before_filter :login_required
41
+ #
42
+ # To require logins for specific actions, use this in your controllers:
43
+ #
44
+ # before_filter :login_required, :only => [ :edit, :update ]
45
+ #
46
+ # To skip this in a subclassed controller:
47
+ #
48
+ # skip_before_filter :login_required
49
+ #
50
+ def login_required
51
+ username, passwd = get_auth_data
52
+ self.current_<%= file_name %> ||= <%= class_name %>.authenticate(username, passwd) || :false if username && passwd
53
+ logged_in? && authorized? ? true : 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 <%= file_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
+ respond_to do |accepts|
66
+ accepts.html do
67
+ store_location
68
+ redirect_to :controller => '<%= controller_file_name %>', :action => 'new'
69
+ end
70
+ accepts.xml do
71
+ headers["Status"] = "Unauthorized"
72
+ headers["WWW-Authenticate"] = %(Basic realm="Web Password")
73
+ render :text => "Could't authenticate you", :status => '401 Unauthorized'
74
+ end
75
+ end
76
+ false
77
+ end
78
+
79
+ # Store the URI of the current request in the session.
80
+ #
81
+ # We can return to this location by calling #redirect_back_or_default.
82
+ def store_location
83
+ session[:return_to] = request.request_uri
84
+ end
85
+
86
+ # Redirect to the URI stored by the most recent store_location call or
87
+ # to the passed default.
88
+ def redirect_back_or_default(default)
89
+ session[:return_to] ? redirect_to_url(session[:return_to]) : redirect_to(default)
90
+ session[:return_to] = nil
91
+ end
92
+
93
+ # Inclusion hook to make #current_<%= file_name %> and #logged_in?
94
+ # available as ActionView helper methods.
95
+ def self.included(base)
96
+ base.send :helper_method, :current_<%= file_name %>, :logged_in?
97
+ end
98
+
99
+ # When called with before_filter :login_from_cookie will check for an :auth_token
100
+ # cookie and log the user back in if apropriate
101
+ def login_from_cookie
102
+ return unless cookies[:auth_token] && !logged_in?
103
+ user = <%= class_name %>.find_by_remember_token(cookies[:auth_token])
104
+ if user && user.remember_token?
105
+ user.remember_me
106
+ self.current_<%= file_name %> = user
107
+ cookies[:auth_token] = { :value => self.current_<%= file_name %>.remember_token , :expires => self.current_<%= file_name %>.remember_token_expires_at }
108
+ flash[:notice] = "Logged in successfully"
109
+ end
110
+ end
111
+
112
+ private
113
+ @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
114
+ # gets BASIC auth info
115
+ def get_auth_data
116
+ auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
117
+ auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
118
+ return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
119
+ end
120
+ end
@@ -0,0 +1,113 @@
1
+ module AuthenticatedTestHelper
2
+ # Sets the current <%= file_name %> in the session from the <%= file_name %> fixtures.
3
+ def login_as(<%= file_name %>)
4
+ @request.session[:<%= file_name %>] = <%= file_name %> ? <%= table_name %>(<%= file_name %>).id : nil
5
+ end
6
+
7
+ def content_type(type)
8
+ @request.env['Content-Type'] = type
9
+ end
10
+
11
+ def accept(accept)
12
+ @request.env["HTTP_ACCEPT"] = accept
13
+ end
14
+
15
+ def authorize_as(user)
16
+ if user
17
+ @request.env["HTTP_AUTHORIZATION"] = "Basic #{Base64.encode64("#{users(user).login}:test")}"
18
+ accept 'application/xml'
19
+ content_type 'application/xml'
20
+ else
21
+ @request.env["HTTP_AUTHORIZATION"] = nil
22
+ accept nil
23
+ content_type nil
24
+ end
25
+ end
26
+
27
+ # http://project.ioni.st/post/217#post-217
28
+ #
29
+ # def test_new_publication
30
+ # assert_difference(Publication, :count) do
31
+ # post :create, :publication => {...}
32
+ # # ...
33
+ # end
34
+ # end
35
+ #
36
+ def assert_difference(object, method = nil, difference = 1)
37
+ initial_value = object.send(method)
38
+ yield
39
+ assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
40
+ end
41
+
42
+ def assert_no_difference(object, method, &block)
43
+ assert_difference object, method, 0, &block
44
+ end
45
+
46
+ # Assert the block redirects to the login
47
+ #
48
+ # assert_requires_login(:bob) { |c| c.get :edit, :id => 1 }
49
+ #
50
+ def assert_requires_login(login = nil)
51
+ yield HttpLoginProxy.new(self, login)
52
+ end
53
+
54
+ def assert_http_authentication_required(login = nil)
55
+ yield XmlLoginProxy.new(self, login)
56
+ end
57
+
58
+ def reset!(*instance_vars)
59
+ instance_vars = [:controller, :request, :response] unless instance_vars.any?
60
+ instance_vars.collect! { |v| "@#{v}".to_sym }
61
+ instance_vars.each do |var|
62
+ instance_variable_set(var, instance_variable_get(var).class.new)
63
+ end
64
+ end
65
+ end
66
+
67
+ class BaseLoginProxy
68
+ attr_reader :controller
69
+ attr_reader :options
70
+ def initialize(controller, login)
71
+ @controller = controller
72
+ @login = login
73
+ end
74
+
75
+ private
76
+ def authenticated
77
+ raise NotImplementedError
78
+ end
79
+
80
+ def check
81
+ raise NotImplementedError
82
+ end
83
+
84
+ def method_missing(method, *args)
85
+ @controller.reset!
86
+ authenticate
87
+ @controller.send(method, *args)
88
+ check
89
+ end
90
+ end
91
+
92
+ class HttpLoginProxy < BaseLoginProxy
93
+ protected
94
+ def authenticate
95
+ @controller.login_as @login if @login
96
+ end
97
+
98
+ def check
99
+ @controller.assert_redirected_to :controller => 'sessions', :action => 'new'
100
+ end
101
+ end
102
+
103
+ class XmlLoginProxy < BaseLoginProxy
104
+ protected
105
+ def authenticate
106
+ @controller.accept 'application/xml'
107
+ @controller.authorize_as @login if @login
108
+ end
109
+
110
+ def check
111
+ @controller.assert_response 401
112
+ end
113
+ end