tfe-authlogic_openid 0.1.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.
Files changed (39) hide show
  1. data/CHANGELOG.rdoc +25 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +38 -0
  4. data/README.rdoc +93 -0
  5. data/Rakefile +137 -0
  6. data/init.rb +1 -0
  7. data/lib/authlogic_openid.rb +6 -0
  8. data/lib/authlogic_openid/acts_as_authentic.rb +174 -0
  9. data/lib/authlogic_openid/session.rb +125 -0
  10. data/lib/authlogic_openid/version.rb +51 -0
  11. data/test/acts_as_authentic_test.rb +105 -0
  12. data/test/fixtures/users.yml +9 -0
  13. data/test/libs/open_id_authentication/CHANGELOG +35 -0
  14. data/test/libs/open_id_authentication/README +231 -0
  15. data/test/libs/open_id_authentication/Rakefile +22 -0
  16. data/test/libs/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb +11 -0
  17. data/test/libs/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb +20 -0
  18. data/test/libs/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb +26 -0
  19. data/test/libs/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb +11 -0
  20. data/test/libs/open_id_authentication/init.rb +18 -0
  21. data/test/libs/open_id_authentication/lib/open_id_authentication.rb +244 -0
  22. data/test/libs/open_id_authentication/lib/open_id_authentication/association.rb +9 -0
  23. data/test/libs/open_id_authentication/lib/open_id_authentication/db_store.rb +55 -0
  24. data/test/libs/open_id_authentication/lib/open_id_authentication/mem_cache_store.rb +73 -0
  25. data/test/libs/open_id_authentication/lib/open_id_authentication/nonce.rb +5 -0
  26. data/test/libs/open_id_authentication/lib/open_id_authentication/request.rb +23 -0
  27. data/test/libs/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb +20 -0
  28. data/test/libs/open_id_authentication/tasks/open_id_authentication_tasks.rake +30 -0
  29. data/test/libs/open_id_authentication/test/mem_cache_store_test.rb +151 -0
  30. data/test/libs/open_id_authentication/test/normalize_test.rb +32 -0
  31. data/test/libs/open_id_authentication/test/open_id_authentication_test.rb +46 -0
  32. data/test/libs/open_id_authentication/test/status_test.rb +14 -0
  33. data/test/libs/open_id_authentication/test/test_helper.rb +17 -0
  34. data/test/libs/rails_trickery.rb +41 -0
  35. data/test/libs/user.rb +3 -0
  36. data/test/libs/user_session.rb +2 -0
  37. data/test/session_test.rb +32 -0
  38. data/test/test_helper.rb +78 -0
  39. metadata +107 -0
@@ -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 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 open_id_authentication plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'OpenIdAuthentication'
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,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,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,244 @@
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
+ # if an XRI has a prefix, strip it.
82
+ identifier.gsub!(/xri:\/\//i, '')
83
+
84
+ # dodge XRIs -- TODO: validate, don't just skip.
85
+ unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
86
+ # does it begin with http? if not, add it.
87
+ identifier = "http://#{identifier}" unless identifier =~ /^http/i
88
+
89
+ # strip any fragments
90
+ identifier.gsub!(/\#(.*)$/, '')
91
+
92
+ begin
93
+ uri = URI.parse(identifier)
94
+ uri.scheme = uri.scheme.downcase # URI should do this
95
+ identifier = uri.normalize.to_s
96
+ rescue URI::InvalidURIError
97
+ raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
98
+ end
99
+ end
100
+
101
+ return identifier
102
+ end
103
+
104
+ # deprecated for OpenID 2.0, where not all OpenIDs are URLs
105
+ def self.normalize_url(url)
106
+ ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead"
107
+ self.normalize_identifier(url)
108
+ end
109
+
110
+ protected
111
+ def normalize_url(url)
112
+ OpenIdAuthentication.normalize_url(url)
113
+ end
114
+
115
+ def normalize_identifier(url)
116
+ OpenIdAuthentication.normalize_identifier(url)
117
+ end
118
+
119
+ # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier"
120
+ # because that's what the specification dictates in order to get browser auto-complete working across sites
121
+ def using_open_id?(identity_url = nil) #:doc:
122
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
123
+ !identity_url.blank? || params[:open_id_complete]
124
+ end
125
+
126
+ def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc:
127
+ identity_url ||= params[:openid_identifier] || params[:openid_url]
128
+
129
+ if params[:open_id_complete].nil?
130
+ begin_open_id_authentication(identity_url, options, &block)
131
+ else
132
+ complete_open_id_authentication(&block)
133
+ end
134
+ end
135
+
136
+ private
137
+ def begin_open_id_authentication(identity_url, options = {})
138
+ identity_url = normalize_identifier(identity_url)
139
+ return_to = options.delete(:return_to)
140
+ method = options.delete(:method)
141
+
142
+ options[:required] ||= [] # reduces validation later
143
+ options[:optional] ||= []
144
+
145
+ open_id_request = open_id_consumer.begin(identity_url)
146
+ add_simple_registration_fields(open_id_request, options)
147
+ add_ax_fields(open_id_request, options)
148
+
149
+ redirect_to(open_id_redirect_url(open_id_request, return_to, method))
150
+ rescue OpenIdAuthentication::InvalidOpenId => e
151
+ yield Result[:invalid], identity_url, nil
152
+ rescue OpenID::OpenIDError, Timeout::Error => e
153
+ logger.error("[OPENID] #{e}")
154
+ yield Result[:missing], identity_url, nil
155
+ end
156
+
157
+ def complete_open_id_authentication
158
+ params_with_path = params.reject { |key, value| request.path_parameters[key] }
159
+ params_with_path.delete(:format)
160
+ open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
161
+ identity_url = normalize_identifier(open_id_response.display_identifier) if open_id_response.display_identifier
162
+
163
+ case open_id_response.status
164
+ when OpenID::Consumer::SUCCESS
165
+ profile_data = {}
166
+
167
+ # merge the SReg data and the AX data into a single hash of profile data
168
+ [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response|
169
+ if data_response.from_success_response( open_id_response )
170
+ profile_data.merge! data_response.from_success_response( open_id_response ).data
171
+ end
172
+ end
173
+
174
+ yield Result[:successful], identity_url, profile_data
175
+ when OpenID::Consumer::CANCEL
176
+ yield Result[:canceled], identity_url, nil
177
+ when OpenID::Consumer::FAILURE
178
+ yield Result[:failed], identity_url, nil
179
+ when OpenID::Consumer::SETUP_NEEDED
180
+ yield Result[:setup_needed], open_id_response.setup_url, nil
181
+ end
182
+ end
183
+
184
+ def open_id_consumer
185
+ OpenID::Consumer.new(session, OpenIdAuthentication.store)
186
+ end
187
+
188
+ def add_simple_registration_fields(open_id_request, fields)
189
+ sreg_request = OpenID::SReg::Request.new
190
+
191
+ # filter out AX identifiers (URIs)
192
+ required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
193
+ optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact
194
+
195
+ sreg_request.request_fields(required_fields, true) unless required_fields.blank?
196
+ sreg_request.request_fields(optional_fields, false) unless optional_fields.blank?
197
+ sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
198
+ open_id_request.add_extension(sreg_request)
199
+ end
200
+
201
+ def add_ax_fields( open_id_request, fields )
202
+ ax_request = OpenID::AX::FetchRequest.new
203
+
204
+ # look through the :required and :optional fields for URIs (AX identifiers)
205
+ fields[:required].each do |f|
206
+ next unless f =~ /^https?:\/\//
207
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) )
208
+ end
209
+
210
+ fields[:optional].each do |f|
211
+ next unless f =~ /^https?:\/\//
212
+ ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) )
213
+ end
214
+
215
+ open_id_request.add_extension( ax_request )
216
+ end
217
+
218
+ def open_id_redirect_url(open_id_request, return_to = nil, method = nil)
219
+ open_id_request.return_to_args['_method'] = (method || request.method).to_s
220
+ open_id_request.return_to_args['open_id_complete'] = '1'
221
+ open_id_request.redirect_url(root_url, return_to || requested_url)
222
+ end
223
+
224
+ def requested_url
225
+ relative_url_root = self.class.respond_to?(:relative_url_root) ?
226
+ self.class.relative_url_root.to_s :
227
+ request.relative_url_root
228
+ "#{request.protocol}#{request.host_with_port}#{ActionController::Base.relative_url_root}#{request.path}"
229
+ end
230
+
231
+ def timeout_protection_from_identity_server
232
+ yield
233
+ rescue Timeout::Error
234
+ Class.new do
235
+ def status
236
+ OpenID::FAILURE
237
+ end
238
+
239
+ def msg
240
+ "Identity server timed out"
241
+ end
242
+ end.new
243
+ end
244
+ 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