tfe-authlogic_openid 0.1.0

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