openid_fu_generator 0.0.1
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/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +48 -0
- data/README.txt +48 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +72 -0
- data/config/requirements.rb +15 -0
- data/lib/openid_fu.rb +10 -0
- data/lib/openid_fu/association.rb +12 -0
- data/lib/openid_fu/controller_methods.rb +97 -0
- data/lib/openid_fu/nonce.rb +5 -0
- data/lib/openid_fu/openid_ar_store.rb +62 -0
- data/lib/openid_fu/openid_attribute_types.rb +79 -0
- data/lib/openid_fu/openid_ax_ext.rb +12 -0
- data/lib/openid_fu/openid_setting.rb +5 -0
- data/lib/openid_fu/version.rb +9 -0
- data/openid_fu_generator.rb +246 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/templates/activation.html.erb +3 -0
- data/templates/authenticated_system.rb +155 -0
- data/templates/authenticated_test_helper.rb +94 -0
- data/templates/controller.rb +111 -0
- data/templates/fixtures.yml +15 -0
- data/templates/functional_test.rb +87 -0
- data/templates/helper.rb +2 -0
- data/templates/login.html.erb +20 -0
- data/templates/migration.rb +55 -0
- data/templates/model.rb +92 -0
- data/templates/model_controller.rb +30 -0
- data/templates/model_functional_test.rb +64 -0
- data/templates/model_helper.rb +2 -0
- data/templates/notifier.rb +25 -0
- data/templates/notifier_test.rb +31 -0
- data/templates/observer.rb +11 -0
- data/templates/open_id_form.html.erb +13 -0
- data/templates/restore_location.html.erb +30 -0
- data/templates/signup.html.erb +19 -0
- data/templates/signup_notification.html.erb +8 -0
- data/templates/unit_test.rb +94 -0
- data/test/test_helper.rb +4 -0
- data/test/test_openid_fu.rb +11 -0
- metadata +108 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
desc 'Release the website and new gem version'
|
2
|
+
task :deploy => [:check_version, :website, :release] do
|
3
|
+
puts "Remember to create SVN tag:"
|
4
|
+
puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
|
5
|
+
"svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
|
6
|
+
puts "Suggested comment:"
|
7
|
+
puts "Tagging release #{CHANGES}"
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
|
11
|
+
task :local_deploy => [:website_generate, :install_gem]
|
12
|
+
|
13
|
+
task :check_version do
|
14
|
+
unless ENV['VERSION']
|
15
|
+
puts 'Must pass a VERSION=x.y.z release version'
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
unless ENV['VERSION'] == VERS
|
19
|
+
puts "Please update your version.rb to match the release version, currently #{VERS}"
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Install the package as a gem, without generating documentation(ri/rdoc)'
|
25
|
+
task :install_gem_no_doc => [:clean, :package] do
|
26
|
+
sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri"
|
27
|
+
end
|
28
|
+
|
29
|
+
namespace :manifest do
|
30
|
+
desc 'Recreate Manifest.txt to include ALL files'
|
31
|
+
task :refresh do
|
32
|
+
`rake check_manifest | patch -p0 > Manifest.txt`
|
33
|
+
end
|
34
|
+
end
|
data/tasks/website.rake
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
desc 'Generate website files'
|
2
|
+
task :website_generate => :ruby_env do
|
3
|
+
(Dir['website/**/*.txt'] - Dir['website/version*.txt']).each do |txt|
|
4
|
+
sh %{ #{RUBY_APP} script/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Upload website files to rubyforge'
|
9
|
+
task :website_upload do
|
10
|
+
host = "#{rubyforge_username}@rubyforge.org"
|
11
|
+
remote_dir = "/var/www/gforge-projects/#{PATH}/"
|
12
|
+
local_dir = 'website'
|
13
|
+
sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate and upload website files'
|
17
|
+
task :website => [:website_generate, :website_upload, :publish_docs]
|
@@ -0,0 +1,155 @@
|
|
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
|
+
return auto_login if request.xhr?
|
69
|
+
redirect_to :controller => '<%= controller_file_name %>',
|
70
|
+
:action => 'new', :open_id_claimed_id => params[:open_id_claimed_id]
|
71
|
+
end
|
72
|
+
accepts.xml do
|
73
|
+
headers["Status"] = "Unauthorized"
|
74
|
+
headers["WWW-Authenticate"] = %(Basic realm="Web Password")
|
75
|
+
render :text => "Could't authenticate you", :status => '401 Unauthorized'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
def auto_login
|
82
|
+
render :update do |page|
|
83
|
+
page << <<-"EOH"
|
84
|
+
(function(){
|
85
|
+
var input, form = document.createElement('form');
|
86
|
+
form.style.position = 'absolute';
|
87
|
+
form.style.left = form.style.top = '-1000px';
|
88
|
+
document.body.appendChild(form);
|
89
|
+
input = form.appendChild(document.createElement('input'));
|
90
|
+
input.name = 'open_id_claimed_id';
|
91
|
+
input.value = #{params[:open_id_claimed_id].to_json};
|
92
|
+
input = form.appendChild(document.createElement('input'));
|
93
|
+
input.name = 'remember_me';
|
94
|
+
input.value = #{params[:remember_me].to_json};
|
95
|
+
input = form.appendChild(document.createElement('input'));
|
96
|
+
input.name = 'authenticity_token';
|
97
|
+
input.value = $$('input[name="authenticity_token"]')[0].value;
|
98
|
+
form.action = #{begin_<%= controller_file_name %>_path.to_json};
|
99
|
+
form.method = 'POST';
|
100
|
+
form.submit();
|
101
|
+
})();
|
102
|
+
EOH
|
103
|
+
end
|
104
|
+
false
|
105
|
+
end
|
106
|
+
|
107
|
+
# Store the URI of the current request in the session.
|
108
|
+
#
|
109
|
+
# We can return to this location by calling #redirect_back_or_default.
|
110
|
+
def store_location
|
111
|
+
session[:stored_parameters] = params
|
112
|
+
session[:return_to] = request.request_uri
|
113
|
+
session[:return_to_method] = request.method
|
114
|
+
end
|
115
|
+
|
116
|
+
# Redirect to the URI stored by the most recent store_location call or
|
117
|
+
# to the passed default.
|
118
|
+
def redirect_back_or_default(default)
|
119
|
+
if session[:return_to]
|
120
|
+
render :action => 'restore_location'
|
121
|
+
else
|
122
|
+
redirect_to(default)
|
123
|
+
end
|
124
|
+
session[:return_to] = nil
|
125
|
+
session[:return_to_method] = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
# Inclusion hook to make #current_<%= file_name %> and #logged_in?
|
129
|
+
# available as ActionView helper methods.
|
130
|
+
def self.included(base)
|
131
|
+
base.send :helper_method, :current_<%= file_name %>, :logged_in?
|
132
|
+
end
|
133
|
+
|
134
|
+
# When called with before_filter :login_from_cookie will check for an :auth_token
|
135
|
+
# cookie and log the user back in if apropriate
|
136
|
+
def login_from_cookie
|
137
|
+
return unless cookies[:auth_token] && !logged_in?
|
138
|
+
user = <%= class_name %>.find_by_remember_token(cookies[:auth_token])
|
139
|
+
if user && user.remember_token?
|
140
|
+
user.remember_me
|
141
|
+
self.current_<%= file_name %> = user
|
142
|
+
cookies[:auth_token] = { :value => self.current_<%= file_name %>.remember_token , :expires => self.current_<%= file_name %>.remember_token_expires_at }
|
143
|
+
flash[:notice] = "Logged in successfully"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
@@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
|
149
|
+
# gets BASIC auth info
|
150
|
+
def get_auth_data
|
151
|
+
auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
|
152
|
+
auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
|
153
|
+
return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,94 @@
|
|
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
|
+
# Assert the block redirects to the login
|
28
|
+
#
|
29
|
+
# assert_requires_login(:bob) { |c| c.get :edit, :id => 1 }
|
30
|
+
#
|
31
|
+
def assert_requires_login(login = nil)
|
32
|
+
yield HttpLoginProxy.new(self, login)
|
33
|
+
end
|
34
|
+
|
35
|
+
def assert_http_authentication_required(login = nil)
|
36
|
+
yield XmlLoginProxy.new(self, login)
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset!(*instance_vars)
|
40
|
+
instance_vars = [:controller, :request, :response] unless instance_vars.any?
|
41
|
+
instance_vars.collect! { |v| "@#{v}".to_sym }
|
42
|
+
instance_vars.each do |var|
|
43
|
+
instance_variable_set(var, instance_variable_get(var).class.new)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class BaseLoginProxy
|
49
|
+
attr_reader :controller
|
50
|
+
attr_reader :options
|
51
|
+
def initialize(controller, login)
|
52
|
+
@controller = controller
|
53
|
+
@login = login
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def authenticated
|
58
|
+
raise NotImplementedError
|
59
|
+
end
|
60
|
+
|
61
|
+
def check
|
62
|
+
raise NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(method, *args)
|
66
|
+
@controller.reset!
|
67
|
+
authenticate
|
68
|
+
@controller.send(method, *args)
|
69
|
+
check
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class HttpLoginProxy < BaseLoginProxy
|
74
|
+
protected
|
75
|
+
def authenticate
|
76
|
+
@controller.login_as @login if @login
|
77
|
+
end
|
78
|
+
|
79
|
+
def check
|
80
|
+
@controller.assert_redirected_to :controller => 'sessions', :action => 'new'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class XmlLoginProxy < BaseLoginProxy
|
85
|
+
protected
|
86
|
+
def authenticate
|
87
|
+
@controller.accept 'application/xml'
|
88
|
+
@controller.authorize_as @login if @login
|
89
|
+
end
|
90
|
+
|
91
|
+
def check
|
92
|
+
@controller.assert_response 401
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# This controller handles the login/logout function of the site.
|
2
|
+
class <%= controller_class_name %>Controller < ApplicationController
|
3
|
+
include OpenidFu::ControllerMethods
|
4
|
+
# XXX: Be sure to include AuthenticationSystem in Application Controller instead
|
5
|
+
include AuthenticatedSystem
|
6
|
+
|
7
|
+
# Obtain personal identity from OP. If OP accomodates the OpenID Attributes Exchange(AX) or SREG,
|
8
|
+
# then use AX or SREG, unfortunately if both OpenID Extensions aren't supported then input ones in RP.
|
9
|
+
#
|
10
|
+
# == Examples:
|
11
|
+
# # Email and nickname are required, gender and birth date are obtained if these are available.
|
12
|
+
# open_id_consumer :required => [:email, :nickname], :if_available => [:gender, :birth_date]
|
13
|
+
#
|
14
|
+
open_id_consumer :required => [:email, :nickname]
|
15
|
+
|
16
|
+
# XXX: If you want "remember me" functionality, add this before_filter to Application Controller
|
17
|
+
before_filter :login_from_cookie
|
18
|
+
|
19
|
+
rescue_from OpenID::DiscoveryFailure, :with => :begin_error
|
20
|
+
|
21
|
+
def new
|
22
|
+
@<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>])
|
23
|
+
end
|
24
|
+
|
25
|
+
def create
|
26
|
+
self.current_<%= file_name %> = <%= class_name %>.authenticate(params[:email], params[:password])
|
27
|
+
if logged_in?
|
28
|
+
if params[:remember_me] == "1"
|
29
|
+
self.current_<%= file_name %>.remember_me
|
30
|
+
cookies[:auth_token] = { :value => self.current_<%= file_name %>.remember_token , :expires => self.current_<%= file_name %>.remember_token_expires_at }
|
31
|
+
end
|
32
|
+
redirect_back_or_default('/')
|
33
|
+
flash[:notice] = "Logged in successfully"
|
34
|
+
else
|
35
|
+
flash[:notice] = "Login failed"
|
36
|
+
render :action => 'new'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy
|
41
|
+
self.current_<%= file_name %>.forget_me if logged_in?
|
42
|
+
cookies.delete :auth_token
|
43
|
+
reset_session
|
44
|
+
flash[:notice] = "You have been logged out."
|
45
|
+
redirect_back_or_default('/')
|
46
|
+
end
|
47
|
+
|
48
|
+
def begin
|
49
|
+
# If the URL was unusable (either because of network conditions, a server error,
|
50
|
+
# or that the response returned was not an OpenID identity page), the library
|
51
|
+
# will return HTTP_FAILURE or PARSE_ERROR. Let the user know that the URL is unusable.
|
52
|
+
case open_id_request.status
|
53
|
+
when OpenID::Consumer::SUCCESS
|
54
|
+
# The URL was a valid identity URL. Now we just need to send a redirect
|
55
|
+
# to the server using the redirect_url the library created for us.
|
56
|
+
|
57
|
+
# redirect to the server
|
58
|
+
redirect_to open_id_request.redirect_url((request.protocol + request.host_with_port + "/"), complete_<%= controller_file_name %>_url)
|
59
|
+
else
|
60
|
+
flash[:error] = "Unable to find OpenID server for <q>#{params[:open_id_claimed_id]}</q>"
|
61
|
+
render :action => :new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def begin_error
|
66
|
+
flash[:error] =
|
67
|
+
"Unable to find OpenID server for <q>#{params[:open_id_claimed_id]}</q>"
|
68
|
+
render :action => :new
|
69
|
+
end
|
70
|
+
|
71
|
+
def complete
|
72
|
+
case open_id_response.status
|
73
|
+
when OpenID::Consumer::FAILURE
|
74
|
+
# In the case of failure, if info is non-nil, it is the URL that we were verifying.
|
75
|
+
# We include it in the error message to help the user figure out what happened.
|
76
|
+
flash[:notice] = if open_id_response.identity_url
|
77
|
+
"Verification of #{open_id_response.identity_url} failed. "
|
78
|
+
else
|
79
|
+
"Verification failed. "
|
80
|
+
end
|
81
|
+
flash[:notice] += open_id_response.msg.to_s
|
82
|
+
when OpenID::Consumer::SUCCESS
|
83
|
+
# Success means that the transaction completed without error. If info is nil,
|
84
|
+
# it means that the user cancelled the verification.
|
85
|
+
flash[:notice] = "You have successfully verified #{open_id_response.identity_url} as your identity."
|
86
|
+
if open_id_fields.any?
|
87
|
+
@<%= file_name %> = <%= class_name %>.find_by_claimed_id(open_id_response.identity_url)
|
88
|
+
@<%= file_name %> ||= <%= class_name %>.new(:claimed_id => open_id_response.identity_url)
|
89
|
+
@<%= file_name %>.email = open_id_fields['email'] if open_id_fields['email']
|
90
|
+
@<%= file_name %>.nickname = open_id_fields['nickname'] if open_id_fields['nickname']
|
91
|
+
if @<%= file_name %>.save
|
92
|
+
self.current_<%= file_name %> = @<%= file_name %>
|
93
|
+
if params[:remember_me] == "1"
|
94
|
+
self.current_<%= file_name %>.remember_me
|
95
|
+
cookies[:auth_token] = { :value => self.current_<%= file_name %>.remember_token , :expires => self.current_<%= file_name %>.remember_token_expires_at }
|
96
|
+
end
|
97
|
+
flash[:notice] = "You have successfully verified #{open_id_response.identity_url} as your identity."
|
98
|
+
return redirect_back_or_default('/')
|
99
|
+
else
|
100
|
+
flash[:notice] = @<%= file_name %>.errors.full_messages.join('<br />')
|
101
|
+
render :action => 'new' and return
|
102
|
+
end
|
103
|
+
end
|
104
|
+
when OpenID::Consumer::CANCEL
|
105
|
+
flash[:notice] = "Verification cancelled."
|
106
|
+
else
|
107
|
+
flash[:notice] = "Unknown response status: #{open_id_response.status}"
|
108
|
+
end
|
109
|
+
redirect_to :action => 'new'
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
quentin:
|
2
|
+
id: 1
|
3
|
+
email: quentin@example.com
|
4
|
+
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
|
5
|
+
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
|
6
|
+
created_at: <%%= 5.days.ago.to_s :db %>
|
7
|
+
<% if options[:include_activation] %> activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b <% end %>
|
8
|
+
<% if options[:include_activation] %> activated_at: <%%= 5.days.ago.to_s :db %> <% end %>
|
9
|
+
aaron:
|
10
|
+
id: 2
|
11
|
+
email: aaron@example.com
|
12
|
+
salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
|
13
|
+
crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
|
14
|
+
created_at: <%%= 1.days.ago.to_s :db %>
|
15
|
+
<% if options[:include_activation] %> activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9a <% end %>
|