pager-restful_open_id_authentication 1.0.20080507

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