openskip-open_id_authentication 1.0.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.
data/CHANGELOG ADDED
@@ -0,0 +1,35 @@
1
+ * Fake HTTP method from OpenID server since they only support a GET. Eliminates the need to set an extra route to match the server's reply. [Josh Peek]
2
+
3
+ * OpenID 2.0 recommends that forms should use the field name "openid_identifier" rather than "openid_url" [Josh Peek]
4
+
5
+ * Return open_id_response.display_identifier to the application instead of .endpoints.claimed_id. [nbibler]
6
+
7
+ * Add Timeout protection [Rick]
8
+
9
+ * An invalid identity url passed through authenticate_with_open_id will no longer raise an InvalidOpenId exception. Instead it will return Result[:missing] to the completion block.
10
+
11
+ * Allow a return_to option to be used instead of the requested url [Josh Peek]
12
+
13
+ * Updated plugin to use Ruby OpenID 2.x.x [Josh Peek]
14
+
15
+ * Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH]
16
+
17
+ * Use URI instead of regexps to normalize the URL and gain free, better matching #8136 [dkubb]
18
+
19
+ * Allow -'s in #normalize_url [Rick]
20
+
21
+ * remove instance of mattr_accessor, it was breaking tests since they don't load ActiveSupport. Fix Timeout test [Rick]
22
+
23
+ * Throw a InvalidOpenId exception instead of just a RuntimeError when the URL can't be normalized [DHH]
24
+
25
+ * Just use the path for the return URL, so extra query parameters don't interfere [DHH]
26
+
27
+ * Added a new default database-backed store after experiencing trouble with the filestore on NFS. The file store is still available as an option [DHH]
28
+
29
+ * Added normalize_url and applied it to all operations going through the plugin [DHH]
30
+
31
+ * Removed open_id? as the idea of using the same input box for both OpenID and username has died -- use using_open_id? instead (which checks for the presence of params[:openid_url] by default) [DHH]
32
+
33
+ * Added OpenIdAuthentication::Result to make it easier to deal with default situations where you don't care to do something particular for each error state [DHH]
34
+
35
+ * Stop relying on root_url being defined, we can just grab the current url instead [DHH]
data/README ADDED
@@ -0,0 +1,231 @@
1
+ OpenIdAuthentication
2
+ ====================
3
+
4
+ Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first:
5
+
6
+ gem install ruby-openid
7
+
8
+ To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb
9
+ from that gem.
10
+
11
+ The specification used is http://openid.net/specs/openid-authentication-2_0.html.
12
+
13
+
14
+ Prerequisites
15
+ =============
16
+
17
+ OpenID authentication uses the session, so be sure that you haven't turned that off. It also relies on a number of
18
+ database tables to store the authentication keys. So you'll have to run the migration to create these before you get started:
19
+
20
+ rake open_id_authentication:db:create
21
+
22
+ Or, use the included generators to install or upgrade:
23
+
24
+ ./script/generate open_id_authentication_tables MigrationName
25
+ ./script/generate upgrade_open_id_authentication_tables MigrationName
26
+
27
+ Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb:
28
+
29
+ OpenIdAuthentication.store = :file
30
+
31
+ This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations.
32
+ If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb.
33
+
34
+ The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb:
35
+
36
+ map.root :controller => 'articles'
37
+
38
+ This plugin relies on Rails Edge revision 6317 or newer.
39
+
40
+
41
+ Example
42
+ =======
43
+
44
+ This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add
45
+ salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point,
46
+ not a destination.
47
+
48
+ Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever
49
+ model you are using for authentication.
50
+
51
+ Also of note is the following code block used in the example below:
52
+
53
+ authenticate_with_open_id do |result, identity_url|
54
+ ...
55
+ end
56
+
57
+ In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' -
58
+ If you are storing just 'example.com' with your user, the lookup will fail.
59
+
60
+ There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs.
61
+
62
+ OpenIdAuthentication.normalize_url(user.identity_url)
63
+
64
+ The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/'
65
+ It will also raise an InvalidOpenId exception if the URL is determined to not be valid.
66
+ Use the above code in your User model and validate OpenID URLs before saving them.
67
+
68
+ config/routes.rb
69
+
70
+ map.root :controller => 'articles'
71
+ map.resource :session
72
+
73
+
74
+ app/views/sessions/new.erb
75
+
76
+ <% form_tag(session_url) do %>
77
+ <p>
78
+ <label for="name">Username:</label>
79
+ <%= text_field_tag "name" %>
80
+ </p>
81
+
82
+ <p>
83
+ <label for="password">Password:</label>
84
+ <%= password_field_tag %>
85
+ </p>
86
+
87
+ <p>
88
+ ...or use:
89
+ </p>
90
+
91
+ <p>
92
+ <label for="openid_identifier">OpenID:</label>
93
+ <%= text_field_tag "openid_identifier" %>
94
+ </p>
95
+
96
+ <p>
97
+ <%= submit_tag 'Sign in', :disable_with => "Signing in&hellip;" %>
98
+ </p>
99
+ <% end %>
100
+
101
+ app/controllers/sessions_controller.rb
102
+ class SessionsController < ApplicationController
103
+ def create
104
+ if using_open_id?
105
+ open_id_authentication
106
+ else
107
+ password_authentication(params[:name], params[:password])
108
+ end
109
+ end
110
+
111
+
112
+ protected
113
+ def password_authentication(name, password)
114
+ if @current_user = @account.users.authenticate(params[:name], params[:password])
115
+ successful_login
116
+ else
117
+ failed_login "Sorry, that username/password doesn't work"
118
+ end
119
+ end
120
+
121
+ def open_id_authentication
122
+ authenticate_with_open_id do |result, identity_url|
123
+ if result.successful?
124
+ if @current_user = @account.users.find_by_identity_url(identity_url)
125
+ successful_login
126
+ else
127
+ failed_login "Sorry, no user by that identity URL exists (#{identity_url})"
128
+ end
129
+ else
130
+ failed_login result.message
131
+ end
132
+ end
133
+ end
134
+
135
+
136
+ private
137
+ def successful_login
138
+ session[:user_id] = @current_user.id
139
+ redirect_to(root_url)
140
+ end
141
+
142
+ def failed_login(message)
143
+ flash[:error] = message
144
+ redirect_to(new_session_url)
145
+ end
146
+ end
147
+
148
+
149
+
150
+ If you're fine with the result messages above and don't need individual logic on a per-failure basis,
151
+ you can collapse the case into a mere boolean:
152
+
153
+ def open_id_authentication
154
+ authenticate_with_open_id do |result, identity_url|
155
+ if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url)
156
+ successful_login
157
+ else
158
+ failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})")
159
+ end
160
+ end
161
+ end
162
+
163
+
164
+ Simple Registration OpenID Extension
165
+ ====================================
166
+
167
+ Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension
168
+
169
+ You can support it in your app by changing #open_id_authentication
170
+
171
+ def open_id_authentication(identity_url)
172
+ # Pass optional :required and :optional keys to specify what sreg fields you want.
173
+ # Be sure to yield registration, a third argument in the #authenticate_with_open_id block.
174
+ authenticate_with_open_id(identity_url,
175
+ :required => [ :nickname, :email ],
176
+ :optional => :fullname) do |result, identity_url, registration|
177
+ case result.status
178
+ when :missing
179
+ failed_login "Sorry, the OpenID server couldn't be found"
180
+ when :invalid
181
+ failed_login "Sorry, but this does not appear to be a valid OpenID"
182
+ when :canceled
183
+ failed_login "OpenID verification was canceled"
184
+ when :failed
185
+ failed_login "Sorry, the OpenID verification failed"
186
+ when :successful
187
+ if @current_user = @account.users.find_by_identity_url(identity_url)
188
+ assign_registration_attributes!(registration)
189
+
190
+ if current_user.save
191
+ successful_login
192
+ else
193
+ failed_login "Your OpenID profile registration failed: " +
194
+ @current_user.errors.full_messages.to_sentence
195
+ end
196
+ else
197
+ failed_login "Sorry, no user by that identity URL exists"
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # registration is a hash containing the valid sreg keys given above
204
+ # use this to map them to fields of your user model
205
+ def assign_registration_attributes!(registration)
206
+ model_to_registration_mapping.each do |model_attribute, registration_attribute|
207
+ unless registration[registration_attribute].blank?
208
+ @current_user.send("#{model_attribute}=", registration[registration_attribute])
209
+ end
210
+ end
211
+ end
212
+
213
+ def model_to_registration_mapping
214
+ { :login => 'nickname', :email => 'email', :display_name => 'fullname' }
215
+ end
216
+
217
+ Attribute Exchange OpenID Extension
218
+ ===================================
219
+
220
+ Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html
221
+
222
+ Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example:
223
+
224
+ authenticate_with_open_id(identity_url,
225
+ :required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration|
226
+
227
+ This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate'
228
+
229
+
230
+
231
+ Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,85 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/packagetask'
5
+ require 'rake/gempackagetask'
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => :test
9
+
10
+ desc 'Test the open_id_authentication plugin.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
16
+
17
+ desc 'Generate documentation for the open_id_authentication plugin.'
18
+ Rake::RDocTask.new(:rdoc) do |rdoc|
19
+ rdoc.rdoc_dir = 'rdoc'
20
+ rdoc.title = 'OpenIdAuthentication'
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ rdoc.rdoc_files.include('README')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
25
+
26
+ NAME = ENV["GEMNAME"] || "open_id_authentication"
27
+ DESCRIPTION = "Provides a thin wrapper around the excellent ruby-openid gem from JanRan. gem version is unofficial, github fork of rails's by moro"
28
+ AUTHOR = "David Heinemeier Hansson"
29
+ EMAIL = ""
30
+ HOMEPAGE = "http://github.com/rails/open_id_authentication"
31
+
32
+ RDOC_OPTS = [
33
+ '--title', "#{NAME} documentation",
34
+ "--charset", "utf-8",
35
+ "--opname", "index.html",
36
+ "--line-numbers",
37
+ "--main", "README",
38
+ "--inline-source",
39
+ ]
40
+
41
+ spec = Gem::Specification.new do |s|
42
+ s.name = NAME
43
+ s.version = "1.0.0"
44
+ s.platform = Gem::Platform::RUBY
45
+ s.has_rdoc = true
46
+ s.extra_rdoc_files = ["README", "CHANGELOG"]
47
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
48
+ s.summary = DESCRIPTION
49
+ s.description = DESCRIPTION
50
+ s.author = AUTHOR
51
+ s.email = EMAIL
52
+ s.homepage = HOMEPAGE
53
+ s.executables = []
54
+ s.require_path = "lib"
55
+ s.test_files = Dir["test/*_test.rb"]
56
+
57
+ s.add_dependency("ruby-openid", ">=2.0.4")
58
+ #s.add_dependency('activesupport', '>=1.3.1')
59
+ #s.required_ruby_version = '>= 1.8.2'
60
+
61
+ s.files = %w(README CHANGELOG Rakefile) +
62
+ Dir.glob("{bin,doc,test,lib,templates,generators,extras,website,script}/**/*") +
63
+ Dir.glob("ext/**/*.{h,c,rb}") +
64
+ Dir.glob("examples/**/*.rb") +
65
+ Dir.glob("tools/*.rb") +
66
+ Dir.glob("rails/*.rb")
67
+
68
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
69
+ end
70
+
71
+ Rake::GemPackageTask.new(spec) do |p|
72
+ p.need_tar = true
73
+ p.gem_spec = spec
74
+ end
75
+
76
+ desc 'Show information about the gem.'
77
+ task :debug_gem do
78
+ puts spec.to_ruby
79
+ end
80
+
81
+ desc 'Update gem spec'
82
+ task :gemspec do
83
+ open("#{NAME}.gemspec", 'w').write spec.to_ruby
84
+ end
85
+
@@ -0,0 +1,11 @@
1
+ class OpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
2
+ def initialize(runtime_args, runtime_options = {})
3
+ super
4
+ end
5
+
6
+ def manifest
7
+ record do |m|
8
+ m.migration_template 'migration.rb', 'db/migrate'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ class <%= class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :open_id_authentication_associations, :force => true do |t|
4
+ t.integer :issued, :lifetime
5
+ t.string :handle, :assoc_type
6
+ t.binary :server_url, :secret
7
+ end
8
+
9
+ create_table :open_id_authentication_nonces, :force => true do |t|
10
+ t.integer :timestamp, :null => false
11
+ t.string :server_url, :null => true
12
+ t.string :salt, :null => false
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table :open_id_authentication_associations
18
+ drop_table :open_id_authentication_nonces
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ class <%= class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ drop_table :open_id_authentication_settings
4
+ drop_table :open_id_authentication_nonces
5
+
6
+ create_table :open_id_authentication_nonces, :force => true do |t|
7
+ t.integer :timestamp, :null => false
8
+ t.string :server_url, :null => true
9
+ t.string :salt, :null => false
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :open_id_authentication_nonces
15
+
16
+ create_table :open_id_authentication_nonces, :force => true do |t|
17
+ t.integer :created
18
+ t.string :nonce
19
+ end
20
+
21
+ create_table :open_id_authentication_settings, :force => true do |t|
22
+ t.string :setting
23
+ t.binary :value
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ class UpgradeOpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase
2
+ def initialize(runtime_args, runtime_options = {})
3
+ super
4
+ end
5
+
6
+ def manifest
7
+ record do |m|
8
+ m.migration_template 'migration.rb', 'db/migrate'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module OpenIdAuthentication
2
+ class Association < ActiveRecord::Base
3
+ set_table_name :open_id_authentication_associations
4
+
5
+ def from_record
6
+ OpenID::Association.new(handle, secret, issued, lifetime, assoc_type)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ require 'openid/store/interface'
2
+
3
+ module OpenIdAuthentication
4
+ class DbStore < OpenID::Store::Interface
5
+ def self.cleanup_nonces
6
+ now = Time.now.to_i
7
+ Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew])
8
+ end
9
+
10
+ def self.cleanup_associations
11
+ now = Time.now.to_i
12
+ Association.delete_all(['issued + lifetime > ?',now])
13
+ end
14
+
15
+ def store_association(server_url, assoc)
16
+ remove_association(server_url, assoc.handle)
17
+ Association.create(:server_url => server_url,
18
+ :handle => assoc.handle,
19
+ :secret => assoc.secret,
20
+ :issued => assoc.issued,
21
+ :lifetime => assoc.lifetime,
22
+ :assoc_type => assoc.assoc_type)
23
+ end
24
+
25
+ def get_association(server_url, handle = nil)
26
+ assocs = if handle.blank?
27
+ Association.find_all_by_server_url(server_url)
28
+ else
29
+ Association.find_all_by_server_url_and_handle(server_url, handle)
30
+ end
31
+
32
+ assocs.reverse.each do |assoc|
33
+ a = assoc.from_record
34
+ if a.expires_in == 0
35
+ assoc.destroy
36
+ else
37
+ return a
38
+ end
39
+ end if assocs.any?
40
+
41
+ return nil
42
+ end
43
+
44
+ def remove_association(server_url, handle)
45
+ Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0
46
+ end
47
+
48
+ def use_nonce(server_url, timestamp, salt)
49
+ return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt)
50
+ return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
51
+ Nonce.create(:server_url => server_url, :timestamp => timestamp, :salt => salt)
52
+ return true
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,73 @@
1
+ require 'digest/sha1'
2
+ require 'openid/store/interface'
3
+
4
+ module OpenIdAuthentication
5
+ class MemCacheStore < OpenID::Store::Interface
6
+ def initialize(*addresses)
7
+ @connection = ActiveSupport::Cache::MemCacheStore.new(addresses)
8
+ end
9
+
10
+ def store_association(server_url, assoc)
11
+ server_key = association_server_key(server_url)
12
+ assoc_key = association_key(server_url, assoc.handle)
13
+
14
+ assocs = @connection.read(server_key) || {}
15
+ assocs[assoc.issued] = assoc_key
16
+
17
+ @connection.write(server_key, assocs)
18
+ @connection.write(assoc_key, assoc, :expires_in => assoc.lifetime)
19
+ end
20
+
21
+ def get_association(server_url, handle = nil)
22
+ if handle
23
+ @connection.read(association_key(server_url, handle))
24
+ else
25
+ server_key = association_server_key(server_url)
26
+ assocs = @connection.read(server_key)
27
+ return if assocs.nil?
28
+
29
+ last_key = assocs[assocs.keys.sort.last]
30
+ @connection.read(last_key)
31
+ end
32
+ end
33
+
34
+ def remove_association(server_url, handle)
35
+ server_key = association_server_key(server_url)
36
+ assoc_key = association_key(server_url, handle)
37
+ assocs = @connection.read(server_key)
38
+
39
+ return false unless assocs && assocs.has_value?(assoc_key)
40
+
41
+ assocs = assocs.delete_if { |key, value| value == assoc_key }
42
+
43
+ @connection.write(server_key, assocs)
44
+ @connection.delete(assoc_key)
45
+
46
+ return true
47
+ end
48
+
49
+ def use_nonce(server_url, timestamp, salt)
50
+ return false if @connection.read(nonce_key(server_url, salt))
51
+ return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew
52
+ @connection.write(nonce_key(server_url, salt), timestamp, :expires_in => OpenID::Nonce.skew)
53
+ return true
54
+ end
55
+
56
+ private
57
+ def association_key(server_url, handle = nil)
58
+ "openid_association_#{digest(server_url)}_#{digest(handle)}"
59
+ end
60
+
61
+ def association_server_key(server_url)
62
+ "openid_association_server_#{digest(server_url)}"
63
+ end
64
+
65
+ def nonce_key(server_url, salt)
66
+ "openid_nonce_#{digest(server_url)}_#{digest(salt)}"
67
+ end
68
+
69
+ def digest(text)
70
+ Digest::SHA1.hexdigest(text)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,5 @@
1
+ module OpenIdAuthentication
2
+ class Nonce < ActiveRecord::Base
3
+ set_table_name :open_id_authentication_nonces
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module OpenIdAuthentication
2
+ module Request
3
+ def self.included(base)
4
+ base.alias_method_chain :request_method, :openid
5
+ end
6
+
7
+ def request_method_with_openid
8
+ if !parameters[:_method].blank? && parameters[:open_id_complete] == '1'
9
+ parameters[:_method].to_sym
10
+ else
11
+ request_method_without_openid
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # In Rails 2.3, the request object has been renamed
18
+ # from AbstractRequest to Request
19
+ if defined? ActionController::Request
20
+ ActionController::Request.send :include, OpenIdAuthentication::Request
21
+ else
22
+ ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request
23
+ end
@@ -0,0 +1,20 @@
1
+ # http://trac.openidenabled.com/trac/ticket/156
2
+ module OpenID
3
+ @@timeout_threshold = 20
4
+
5
+ def self.timeout_threshold
6
+ @@timeout_threshold
7
+ end
8
+
9
+ def self.timeout_threshold=(value)
10
+ @@timeout_threshold = value
11
+ end
12
+
13
+ class StandardFetcher
14
+ def make_http(uri)
15
+ http = @proxy.new(uri.host, uri.port)
16
+ http.read_timeout = http.open_timeout = OpenID.timeout_threshold
17
+ http
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,240 @@
1
+ require 'uri'
2
+ require 'openid/extensions/sreg'
3
+ require 'openid/extensions/ax'
4
+ require 'openid/store/filesystem'
5
+
6
+ require File.dirname(__FILE__) + '/open_id_authentication/association'
7
+ require File.dirname(__FILE__) + '/open_id_authentication/nonce'
8
+ require File.dirname(__FILE__) + '/open_id_authentication/db_store'
9
+ require File.dirname(__FILE__) + '/open_id_authentication/mem_cache_store'
10
+ require File.dirname(__FILE__) + '/open_id_authentication/request'
11
+ require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4"
12
+
13
+ module OpenIdAuthentication
14
+ OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
15
+
16
+ def self.store
17
+ @@store
18
+ end
19
+
20
+ def self.store=(*store_option)
21
+ store, *parameters = *([ store_option ].flatten)
22
+
23
+ @@store = case store
24
+ when :db
25
+ OpenIdAuthentication::DbStore.new
26
+ when :mem_cache
27
+ OpenIdAuthentication::MemCacheStore.new(*parameters)
28
+ when :file
29
+ OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
30
+ else
31
+ raise "Unknown store: #{store}"
32
+ end
33
+ end
34
+
35
+ self.store = :db
36
+
37
+ class InvalidOpenId < StandardError
38
+ end
39
+
40
+ class Result
41
+ ERROR_MESSAGES = {
42
+ :missing => "Sorry, the OpenID server couldn't be found",
43
+ :invalid => "Sorry, but this does not appear to be a valid OpenID",
44
+ :canceled => "OpenID verification was canceled",
45
+ :failed => "OpenID verification failed",
46
+ :setup_needed => "OpenID verification needs setup"
47
+ }
48
+
49
+ def self.[](code)
50
+ new(code)
51
+ end
52
+
53
+ def initialize(code)
54
+ @code = code
55
+ end
56
+
57
+ def status
58
+ @code
59
+ end
60
+
61
+ ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
62
+
63
+ def successful?
64
+ @code == :successful
65
+ end
66
+
67
+ def unsuccessful?
68
+ ERROR_MESSAGES.keys.include?(@code)
69
+ end
70
+
71
+ def message
72
+ ERROR_MESSAGES[@code]
73
+ end
74
+ end
75
+
76
+ # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
77
+ def self.normalize_identifier(identifier)
78
+ # clean up whitespace
79
+ identifier = identifier.to_s.strip
80
+
81
+ case OpenID::Yadis::XRI.identifier_scheme(identifier)
82
+ when :xri
83
+ identifier.gsub!(/xri:\/\//i, '')
84
+ else
85
+ identifier = "http://#{identifier}" unless identifier =~ /^http/i
86
+
87
+ # strip any fragments
88
+ identifier.gsub!(/\#(.*)$/, '')
89
+
90
+ begin
91
+ uri = URI.parse(identifier)
92
+ uri.scheme = uri.scheme.downcase # URI should do this
93
+ identifier = uri.normalize.to_s
94
+ rescue URI::InvalidURIError
95
+ raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
96
+ end
97
+ end
98
+
99
+ return identifier
100
+ end
101
+
102
+ # deprecated for OpenID 2.0, where not all OpenIDs are URLs
103
+ def self.normalize_url(url)
104
+ ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
105
+ self.normalize_identifier(url)
106
+ end
107
+
108
+ protected
109
+ def normalize_url(url)
110
+ OpenIdAuthentication.normalize_url(url)
111
+ end
112
+
113
+ def normalize_identifier(url)
114
+ OpenIdAuthentication.normalize_identifier(url)
115
+ end
116
+
117
+ # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
118
+ # because that's what the specification dictates in order to get browser auto-complete working across sites
119
+ def using_open_id?(identity_url = nil) #:doc:
120
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
121
+ !identity_url.blank? || params[:open_id_complete]
122
+ end
123
+
124
+ def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
125
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
126
+
127
+ if params[:open_id_complete].nil?
128
+ begin_open_id_authentication(identity_url, options, &block)
129
+ else
130
+ complete_open_id_authentication(&block)
131
+ end
132
+ end
133
+
134
+ private
135
+ def begin_open_id_authentication(identity_url, options = {})
136
+ identity_url = normalize_identifier(identity_url)
137
+ return_to = options.delete(:return_to)
138
+ method = options.delete(:method)
139
+
140
+ options[:required] ||= [] # reduces validation later
141
+ options[:optional] ||= []
142
+
143
+ open_id_request = open_id_consumer.begin(identity_url)
144
+ add_simple_registration_fields(open_id_request, options)
145
+ add_ax_fields(open_id_request, options)
146
+ redirect_to(open_id_redirect_url(open_id_request, return_to, method))
147
+ rescue OpenIdAuthentication::InvalidOpenId => e
148
+ yield Result[:invalid], identity_url, nil
149
+ rescue OpenID::OpenIDError, Timeout::Error => e
150
+ logger.error("[OPENID] #{e}")
151
+ yield Result[:missing], identity_url, nil
152
+ end
153
+
154
+ def complete_open_id_authentication
155
+ params_with_path = params.reject { |key, value| request.path_parameters[key] }
156
+ params_with_path.delete(:format)
157
+ open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
158
+
159
+ case open_id_response.status
160
+ when OpenID::Consumer::SUCCESS
161
+ profile_data = {}
162
+
163
+ # merge the SReg data and the AX data into a single hash of profile data
164
+ [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
165
+ if data_response.from_success_response( open_id_response )
166
+ profile_data.merge! data_response.from_success_response( open_id_response ).data
167
+ end
168
+ end
169
+
170
+ yield Result[:successful], open_id_response.identity_url, profile_data
171
+ when OpenID::Consumer::CANCEL
172
+ yield Result[:canceled], open_id_response.identity_url, nil
173
+ when OpenID::Consumer::FAILURE
174
+ yield Result[:failed], open_id_response.identity_url, nil
175
+ when OpenID::Consumer::SETUP_NEEDED
176
+ yield Result[:setup_needed], open_id_response.setup_url, nil
177
+ end
178
+ end
179
+
180
+ def open_id_consumer
181
+ OpenID::Consumer.new(session, OpenIdAuthentication.store)
182
+ end
183
+
184
+ def add_simple_registration_fields(open_id_request, fields)
185
+ sreg_request = OpenID::SReg::Request.new
186
+
187
+ # filter out AX identifiers (URIs)
188
+ required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
189
+ optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
190
+
191
+ sreg_request.request_fields(required_fields, true) unless required_fields.blank?
192
+ sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
193
+ sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
194
+ open_id_request.add_extension(sreg_request)
195
+ end
196
+
197
+ def add_ax_fields( open_id_request, fields )
198
+ ax_request = OpenID::AX::FetchRequest.new
199
+
200
+ # look through the :required and :optional fields for URIs (AX identifiers)
201
+ fields[:required].each do |f|
202
+ next unless f =~ /^https?:\/\//
203
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
204
+ end
205
+
206
+ fields[:optional].each do |f|
207
+ next unless f =~ /^https?:\/\//
208
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
209
+ end
210
+
211
+ open_id_request.add_extension( ax_request )
212
+ end
213
+
214
+ def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
215
+ open_id_request.return_to_args['_method'] = (method || request.method).to_s
216
+ open_id_request.return_to_args['open_id_complete'] = '1'
217
+ open_id_request.redirect_url(root_url, return_to || requested_url)
218
+ end
219
+
220
+ def requested_url
221
+ relative_url_root = self.class.respond_to?(:relative_url_root) ?
222
+ self.class.relative_url_root.to_s :
223
+ request.relative_url_root
224
+ "#{request.protocol}#{request.host_with_port}#{relative_url_root}#{request.path}"
225
+ end
226
+
227
+ def timeout_protection_from_identity_server
228
+ yield
229
+ rescue Timeout::Error
230
+ Class.new do
231
+ def status
232
+ OpenID::FAILURE
233
+ end
234
+
235
+ def msg
236
+ "Identity server timed out"
237
+ end
238
+ end.new
239
+ end
240
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,18 @@
1
+ if config.respond_to?(:gems)
2
+ config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4'
3
+ else
4
+ begin
5
+ require 'openid'
6
+ rescue LoadError
7
+ begin
8
+ gem 'ruby-openid', '>=2.0.4'
9
+ rescue Gem::LoadError
10
+ puts "Install the ruby-openid gem to enable OpenID support"
11
+ end
12
+ end
13
+ end
14
+
15
+ config.to_prepare do
16
+ OpenID::Util.logger = Rails.logger
17
+ ActionController::Base.send :include, OpenIdAuthentication
18
+ end
@@ -0,0 +1,151 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require File.dirname(__FILE__) + '/../lib/open_id_authentication/mem_cache_store'
3
+
4
+ # Mock MemCacheStore with MemoryStore for testing
5
+ class OpenIdAuthentication::MemCacheStore < OpenID::Store::Interface
6
+ def initialize(*addresses)
7
+ @connection = ActiveSupport::Cache::MemoryStore.new
8
+ end
9
+ end
10
+
11
+ class MemCacheStoreTest < Test::Unit::TestCase
12
+ ALLOWED_HANDLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
13
+
14
+ def setup
15
+ @store = OpenIdAuthentication::MemCacheStore.new
16
+ end
17
+
18
+ def test_store
19
+ server_url = "http://www.myopenid.com/openid"
20
+ assoc = gen_assoc(0)
21
+
22
+ # Make sure that a missing association returns no result
23
+ assert_retrieve(server_url)
24
+
25
+ # Check that after storage, getting returns the same result
26
+ @store.store_association(server_url, assoc)
27
+ assert_retrieve(server_url, nil, assoc)
28
+
29
+ # more than once
30
+ assert_retrieve(server_url, nil, assoc)
31
+
32
+ # Storing more than once has no ill effect
33
+ @store.store_association(server_url, assoc)
34
+ assert_retrieve(server_url, nil, assoc)
35
+
36
+ # Removing an association that does not exist returns not present
37
+ assert_remove(server_url, assoc.handle + 'x', false)
38
+
39
+ # Removing an association that does not exist returns not present
40
+ assert_remove(server_url + 'x', assoc.handle, false)
41
+
42
+ # Removing an association that is present returns present
43
+ assert_remove(server_url, assoc.handle, true)
44
+
45
+ # but not present on subsequent calls
46
+ assert_remove(server_url, assoc.handle, false)
47
+
48
+ # Put assoc back in the store
49
+ @store.store_association(server_url, assoc)
50
+
51
+ # More recent and expires after assoc
52
+ assoc2 = gen_assoc(1)
53
+ @store.store_association(server_url, assoc2)
54
+
55
+ # After storing an association with a different handle, but the
56
+ # same server_url, the handle with the later expiration is returned.
57
+ assert_retrieve(server_url, nil, assoc2)
58
+
59
+ # We can still retrieve the older association
60
+ assert_retrieve(server_url, assoc.handle, assoc)
61
+
62
+ # Plus we can retrieve the association with the later expiration
63
+ # explicitly
64
+ assert_retrieve(server_url, assoc2.handle, assoc2)
65
+
66
+ # More recent, and expires earlier than assoc2 or assoc. Make sure
67
+ # that we're picking the one with the latest issued date and not
68
+ # taking into account the expiration.
69
+ assoc3 = gen_assoc(2, 100)
70
+ @store.store_association(server_url, assoc3)
71
+
72
+ assert_retrieve(server_url, nil, assoc3)
73
+ assert_retrieve(server_url, assoc.handle, assoc)
74
+ assert_retrieve(server_url, assoc2.handle, assoc2)
75
+ assert_retrieve(server_url, assoc3.handle, assoc3)
76
+
77
+ assert_remove(server_url, assoc2.handle, true)
78
+
79
+ assert_retrieve(server_url, nil, assoc3)
80
+ assert_retrieve(server_url, assoc.handle, assoc)
81
+ assert_retrieve(server_url, assoc2.handle, nil)
82
+ assert_retrieve(server_url, assoc3.handle, assoc3)
83
+
84
+ assert_remove(server_url, assoc2.handle, false)
85
+ assert_remove(server_url, assoc3.handle, true)
86
+
87
+ assert_retrieve(server_url, nil, assoc)
88
+ assert_retrieve(server_url, assoc.handle, assoc)
89
+ assert_retrieve(server_url, assoc2.handle, nil)
90
+ assert_retrieve(server_url, assoc3.handle, nil)
91
+
92
+ assert_remove(server_url, assoc2.handle, false)
93
+ assert_remove(server_url, assoc.handle, true)
94
+ assert_remove(server_url, assoc3.handle, false)
95
+
96
+ assert_retrieve(server_url, nil, nil)
97
+ assert_retrieve(server_url, assoc.handle, nil)
98
+ assert_retrieve(server_url, assoc2.handle, nil)
99
+ assert_retrieve(server_url, assoc3.handle, nil)
100
+
101
+ assert_remove(server_url, assoc2.handle, false)
102
+ assert_remove(server_url, assoc.handle, false)
103
+ assert_remove(server_url, assoc3.handle, false)
104
+ end
105
+
106
+ def test_nonce
107
+ server_url = "http://www.myopenid.com/openid"
108
+
109
+ [server_url, ''].each do |url|
110
+ nonce1 = OpenID::Nonce::mk_nonce
111
+
112
+ assert_nonce(nonce1, true, url, "#{url}: nonce allowed by default")
113
+ assert_nonce(nonce1, false, url, "#{url}: nonce not allowed twice")
114
+ assert_nonce(nonce1, false, url, "#{url}: nonce not allowed third time")
115
+
116
+ # old nonces shouldn't pass
117
+ old_nonce = OpenID::Nonce::mk_nonce(3600)
118
+ assert_nonce(old_nonce, false, url, "Old nonce #{old_nonce.inspect} passed")
119
+ end
120
+ end
121
+
122
+ private
123
+ def gen_assoc(issued, lifetime = 600)
124
+ secret = OpenID::CryptUtil.random_string(20, nil)
125
+ handle = OpenID::CryptUtil.random_string(128, ALLOWED_HANDLE)
126
+ OpenID::Association.new(handle, secret, Time.now + issued, lifetime, 'HMAC-SHA1')
127
+ end
128
+
129
+ def assert_retrieve(url, handle = nil, expected = nil)
130
+ assoc = @store.get_association(url, handle)
131
+
132
+ if expected.nil?
133
+ assert_nil(assoc)
134
+ else
135
+ assert_equal(expected, assoc)
136
+ assert_equal(expected.handle, assoc.handle)
137
+ assert_equal(expected.secret, assoc.secret)
138
+ end
139
+ end
140
+
141
+ def assert_remove(url, handle, expected)
142
+ present = @store.remove_association(url, handle)
143
+ assert_equal(expected, present)
144
+ end
145
+
146
+ def assert_nonce(nonce, expected, server_url, msg = "")
147
+ stamp, salt = OpenID::Nonce::split_nonce(nonce)
148
+ actual = @store.use_nonce(server_url, stamp, salt)
149
+ assert_equal(expected, actual, msg)
150
+ end
151
+ end
@@ -0,0 +1,32 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class NormalizeTest < Test::Unit::TestCase
4
+ include OpenIdAuthentication
5
+
6
+ NORMALIZATIONS = {
7
+ "openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
8
+ "http://openid.aol.com/nextangler" => "http://openid.aol.com/nextangler",
9
+ "https://openid.aol.com/nextangler" => "https://openid.aol.com/nextangler",
10
+ "HTTP://OPENID.AOL.COM/NEXTANGLER" => "http://openid.aol.com/NEXTANGLER",
11
+ "HTTPS://OPENID.AOL.COM/NEXTANGLER" => "https://openid.aol.com/NEXTANGLER",
12
+ "loudthinking.com" => "http://loudthinking.com/",
13
+ "http://loudthinking.com" => "http://loudthinking.com/",
14
+ "http://loudthinking.com:80" => "http://loudthinking.com/",
15
+ "https://loudthinking.com:443" => "https://loudthinking.com/",
16
+ "http://loudthinking.com:8080" => "http://loudthinking.com:8080/",
17
+ "techno-weenie.net" => "http://techno-weenie.net/",
18
+ "http://techno-weenie.net" => "http://techno-weenie.net/",
19
+ "http://techno-weenie.net " => "http://techno-weenie.net/",
20
+ "=name" => "=name"
21
+ }
22
+
23
+ def test_normalizations
24
+ NORMALIZATIONS.each do |from, to|
25
+ assert_equal to, normalize_identifier(from)
26
+ end
27
+ end
28
+
29
+ def test_broken_open_id
30
+ assert_raises(InvalidOpenId) { normalize_identifier(nil) }
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class OpenIdAuthenticationTest < Test::Unit::TestCase
4
+ def setup
5
+ @controller = Class.new do
6
+ include OpenIdAuthentication
7
+ def params() {} end
8
+ end.new
9
+ end
10
+
11
+ def test_authentication_should_fail_when_the_identity_server_is_missing
12
+ open_id_consumer = mock()
13
+ open_id_consumer.expects(:begin).raises(OpenID::OpenIDError)
14
+ @controller.expects(:open_id_consumer).returns(open_id_consumer)
15
+ @controller.expects(:logger).returns(mock(:error => true))
16
+
17
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
18
+ assert result.missing?
19
+ assert_equal "Sorry, the OpenID server couldn't be found", result.message
20
+ end
21
+ end
22
+
23
+ def test_authentication_should_be_invalid_when_the_identity_url_is_invalid
24
+ @controller.send(:authenticate_with_open_id, "!") do |result, identity_url|
25
+ assert result.invalid?, "Result expected to be invalid but was not"
26
+ assert_equal "Sorry, but this does not appear to be a valid OpenID", result.message
27
+ end
28
+ end
29
+
30
+ def test_authentication_should_fail_when_the_identity_server_times_out
31
+ open_id_consumer = mock()
32
+ open_id_consumer.expects(:begin).raises(Timeout::Error, "Identity Server took too long.")
33
+ @controller.expects(:open_id_consumer).returns(open_id_consumer)
34
+ @controller.expects(:logger).returns(mock(:error => true))
35
+
36
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url|
37
+ assert result.missing?
38
+ assert_equal "Sorry, the OpenID server couldn't be found", result.message
39
+ end
40
+ end
41
+
42
+ def test_authentication_should_begin_when_the_identity_server_is_present
43
+ @controller.expects(:begin_open_id_authentication)
44
+ @controller.send(:authenticate_with_open_id, "http://someone.example.com")
45
+ end
46
+ end
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class StatusTest < Test::Unit::TestCase
4
+ include OpenIdAuthentication
5
+
6
+ def test_state_conditional
7
+ assert Result[:missing].missing?
8
+ assert Result[:missing].unsuccessful?
9
+ assert !Result[:missing].successful?
10
+
11
+ assert Result[:successful].successful?
12
+ assert !Result[:successful].unsuccessful?
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ gem 'activesupport'
5
+ require 'active_support'
6
+
7
+ gem 'actionpack'
8
+ require 'action_controller'
9
+
10
+ gem 'mocha'
11
+ require 'mocha'
12
+
13
+ gem 'ruby-openid'
14
+ require 'openid'
15
+
16
+ RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT
17
+ require File.dirname(__FILE__) + "/../lib/open_id_authentication"
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openskip-open_id_authentication
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Heinemeier Hansson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ruby-openid
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.4
24
+ version:
25
+ description: Provides a thin wrapper around the excellent ruby-openid gem from JanRan. gem version is unofficial, github fork of rails's by moro
26
+ email: ""
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ - CHANGELOG
34
+ files:
35
+ - README
36
+ - CHANGELOG
37
+ - Rakefile
38
+ - test/mem_cache_store_test.rb
39
+ - test/normalize_test.rb
40
+ - test/open_id_authentication_test.rb
41
+ - test/status_test.rb
42
+ - test/test_helper.rb
43
+ - lib/open_id_authentication
44
+ - lib/open_id_authentication/association.rb
45
+ - lib/open_id_authentication/db_store.rb
46
+ - lib/open_id_authentication/mem_cache_store.rb
47
+ - lib/open_id_authentication/nonce.rb
48
+ - lib/open_id_authentication/request.rb
49
+ - lib/open_id_authentication/timeout_fixes.rb
50
+ - lib/open_id_authentication.rb
51
+ - generators/open_id_authentication_tables
52
+ - generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb
53
+ - generators/open_id_authentication_tables/templates
54
+ - generators/open_id_authentication_tables/templates/migration.rb
55
+ - generators/upgrade_open_id_authentication_tables
56
+ - generators/upgrade_open_id_authentication_tables/templates
57
+ - generators/upgrade_open_id_authentication_tables/templates/migration.rb
58
+ - generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb
59
+ - rails/init.rb
60
+ has_rdoc: false
61
+ homepage: http://github.com/rails/open_id_authentication
62
+ post_install_message:
63
+ rdoc_options:
64
+ - --title
65
+ - open_id_authentication documentation
66
+ - --charset
67
+ - utf-8
68
+ - --opname
69
+ - index.html
70
+ - --line-numbers
71
+ - --main
72
+ - README
73
+ - --inline-source
74
+ - --exclude
75
+ - ^(examples|extras)/
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project:
93
+ rubygems_version: 1.2.0
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Provides a thin wrapper around the excellent ruby-openid gem from JanRan. gem version is unofficial, github fork of rails's by moro
97
+ test_files:
98
+ - test/mem_cache_store_test.rb
99
+ - test/normalize_test.rb
100
+ - test/open_id_authentication_test.rb
101
+ - test/status_test.rb