slayer-authlogic_rpx 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.rdoc +46 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest +37 -0
  5. data/README.rdoc +755 -0
  6. data/Rakefile +55 -0
  7. data/generators/add_authlogic_rpx_migration/USAGE +18 -0
  8. data/generators/add_authlogic_rpx_migration/add_authlogic_rpx_migration_generator.rb +44 -0
  9. data/generators/add_authlogic_rpx_migration/templates/migration_internal_mapping.rb +34 -0
  10. data/generators/add_authlogic_rpx_migration/templates/migration_no_mapping.rb +29 -0
  11. data/init.rb +1 -0
  12. data/lib/authlogic_rpx.rb +9 -0
  13. data/lib/authlogic_rpx/acts_as_authentic.rb +297 -0
  14. data/lib/authlogic_rpx/helper.rb +54 -0
  15. data/lib/authlogic_rpx/rpx_identifier.rb +4 -0
  16. data/lib/authlogic_rpx/session.rb +237 -0
  17. data/lib/authlogic_rpx/version.rb +51 -0
  18. data/rails/init.rb +1 -0
  19. data/slayer-authlogic_rpx.gemspec +102 -0
  20. data/test/fixtures/rpxresponses.yml +20 -0
  21. data/test/fixtures/users.yml +20 -0
  22. data/test/integration/basic_authentication_and_registration_test.rb +53 -0
  23. data/test/integration/internal_mapping/basic_authentication_and_registration_test.rb +3 -0
  24. data/test/integration/internal_mapping/settings_test.rb +10 -0
  25. data/test/integration/no_mapping/basic_authentication_and_registration_test.rb +3 -0
  26. data/test/integration/no_mapping/settings_test.rb +10 -0
  27. data/test/libs/ext_test_unit.rb +30 -0
  28. data/test/libs/mock_rpx_now.rb +34 -0
  29. data/test/libs/rails_trickery.rb +41 -0
  30. data/test/libs/rpxresponse.rb +3 -0
  31. data/test/libs/user.rb +3 -0
  32. data/test/libs/user_session.rb +3 -0
  33. data/test/test_helper.rb +85 -0
  34. data/test/test_internal_mapping_helper.rb +93 -0
  35. data/test/unit/acts_as_authentic_settings_test.rb +42 -0
  36. data/test/unit/session_settings_test.rb +38 -0
  37. data/test/unit/session_validation_test.rb +16 -0
  38. data/test/unit/verify_rpx_mock_test.rb +29 -0
  39. metadata +168 -0
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ ENV['RDOCOPT'] = "-S -f html -T hanna"
2
+
3
+ require "rubygems"
4
+ require 'rake'
5
+
6
+ require File.dirname(__FILE__) << "/lib/authlogic_rpx/version"
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gem|
11
+ gem.name = "slayer-authlogic_rpx"
12
+ gem.version = AuthlogicRpx::Version::STRING
13
+ gem.summary = %Q{Authlogic plug-in for RPX support}
14
+ gem.description = %Q{Authlogic extension/plugin that provides RPX (rpxnow.com) authentication support}
15
+ gem.email = "gallagher.paul@gmail.com"
16
+ gem.homepage = "http://github.com/tardate/authlogic_rpx"
17
+ gem.authors = [ "Paul Gallagher / tardate <gallagher.paul@gmail.com>", "Vladislav Moskovets <github@vlad.org.ua>" ]
18
+ gem.add_dependency "authlogic", ">= 3.0.3"
19
+ gem.add_dependency "rpx_now", ">= 0.6.23"
20
+ gem.add_development_dependency "test-unit", ">= 2.1.1"
21
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
22
+ end
23
+ Jeweler::GemcutterTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
26
+ end
27
+
28
+ require 'rake/testtask'
29
+
30
+ namespace :test do
31
+ Rake::TestTask.new(:units) do |t|
32
+ t.libs << "test/libs"
33
+ t.pattern = 'test/unit/*test.rb'
34
+ t.verbose = true
35
+ end
36
+
37
+ Rake::TestTask.new(:no_mapping) do |t|
38
+ t.libs << "test/libs"
39
+ t.test_files = FileList.new('test/unit/*test.rb', 'test/integration/no_mapping/*test.rb')
40
+ t.verbose = true
41
+ end
42
+
43
+ Rake::TestTask.new(:internal_mapping) do |t|
44
+ t.libs << "test/libs"
45
+ t.test_files = FileList.new('test/integration/internal_mapping/*test.rb')
46
+ t.verbose = true
47
+ end
48
+ end
49
+
50
+ task :test do
51
+ Rake::Task['test:no_mapping'].invoke
52
+ Rake::Task['test:internal_mapping'].invoke
53
+ end
54
+
55
+ task :default => :test
@@ -0,0 +1,18 @@
1
+ Description:
2
+ ruby script/generate add_authlogic_rpx_migration [mapping:mapping_mode] [user_model:model_name]
3
+
4
+ Creates an add_authlogic_rpx_migration file in db/migrate.
5
+
6
+ The mapping_mode parameter indicates which style of Authlogic_RPX-supported identity
7
+ mapping should be used. Allowed values for mapping_mode are:
8
+ none
9
+ internal
10
+ Default mapping_mode is 'internal'
11
+
12
+ The user_model parameter specifies the name of the user/member model in your application.
13
+ Default model_name is 'User'
14
+
15
+ e.g. to generate the RPX migration where the user model is called 'Member' and you do not
16
+ want to support identity mapping:
17
+
18
+ ruby script/generate add_authlogic_rpx_migration mapping:none user_model:member
@@ -0,0 +1,44 @@
1
+ class AddAuthlogicRpxMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+
4
+ record do |m|
5
+
6
+ m.migration_template template_name, 'db/migrate', :assigns => {
7
+ :user_model_base => user_model_base,
8
+ :user_model => user_model,
9
+ :user_model_collection => user_model_collection
10
+ }
11
+ end
12
+ end
13
+
14
+ def file_name
15
+ "add_authlogic_rpx_migration"
16
+ end
17
+
18
+ protected
19
+ # Override with your own usage banner.
20
+ def banner
21
+ "Usage: #{$0} #{spec.name} [options] [mapping:mapping_mode] [user_model:model_name]"
22
+ end
23
+
24
+ attr_writer :params
25
+ def params
26
+ @params ||= {"mapping" => "internal", "user_model" => "User"}.merge( Hash[*(@args.collect { |arg| arg.split(":") }.flatten)] )
27
+ end
28
+
29
+ def user_model_base
30
+ params['user_model'].singularize.downcase
31
+ end
32
+ def user_model
33
+ params['user_model'].singularize.capitalize
34
+ end
35
+ def user_model_collection
36
+ params['user_model'].pluralize.downcase
37
+ end
38
+ def mapping
39
+ params['mapping']
40
+ end
41
+ def template_name
42
+ mapping == 'none' ? 'migration_no_mapping.rb' : 'migration_internal_mapping.rb'
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ class AddAuthlogicRpxMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :rpx_identifiers do |t|
4
+ t.string :identifier, :null => false
5
+ t.string :provider_name
6
+ t.integer :<%= user_model_base %>_id, :null => false
7
+ t.timestamps
8
+ end
9
+ add_index :rpx_identifiers, :identifier, :unique => true, :null => false
10
+ add_index :rpx_identifiers, :<%= user_model_base %>_id, :unique => false, :null => false
11
+
12
+ # == Customisation may be required here ==
13
+ # You may need to remove database constraints on other fields if they will be unused in the RPX case
14
+ # (e.g. crypted_password and password_salt to make password authentication optional).
15
+ # If you are using auto-registration, you must also remove any database constraints for fields that will be automatically mapped
16
+ # e.g.:
17
+ #change_column :<%= user_model_collection %>, :crypted_password, :string, :default => nil, :null => true
18
+ #change_column :<%= user_model_collection %>, :password_salt, :string, :default => nil, :null => true
19
+
20
+ end
21
+
22
+ def self.down
23
+ drop_table :rpx_identifiers
24
+
25
+ # == Customisation may be required here ==
26
+ # Restore user model database constraints as appropriate
27
+ # e.g.:
28
+ #[:crypted_password, :password_salt].each do |field|
29
+ # <%= user_model %>.all(:conditions => "#{field} is NULL").each { |user| user.update_attribute(field, "") if user.send(field).nil? }
30
+ # change_column :<%= user_model_collection %>, field, :string, :default => "", :null => false
31
+ #end
32
+
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ class AddAuthlogicRpxMigration < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ add_column :<%= user_model_collection %>, :rpx_identifier, :string
5
+ add_index :<%= user_model_collection %>, :rpx_identifier
6
+
7
+ # == Customisation may be required here ==
8
+ # You may need to remove database constraints on other fields if they will be unused in the RPX case
9
+ # (e.g. crypted_password and password_salt to make password authentication optional).
10
+ # If you are using auto-registration, you must also remove any database constraints for fields that will be automatically mapped
11
+ # e.g.:
12
+ #change_column :<%= user_model_collection %>, :crypted_password, :string, :default => nil, :null => true
13
+ #change_column :<%= user_model_collection %>, :password_salt, :string, :default => nil, :null => true
14
+
15
+ end
16
+
17
+ def self.down
18
+ remove_column :<%= user_model_collection %>, :rpx_identifier
19
+
20
+ # == Customisation may be required here ==
21
+ # Restore user model database constraints as appropriate
22
+ # e.g.:
23
+ #[:crypted_password, :password_salt].each do |field|
24
+ # <%= user_model %>.all(:conditions => "#{field} is NULL").each { |user| user.update_attribute(field, "") if user.send(field).nil? }
25
+ # change_column :<%= user_model_collection %>, field, :string, :default => "", :null => false
26
+ #end
27
+
28
+ end
29
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init.rb"
@@ -0,0 +1,9 @@
1
+ require "authlogic_rpx/version"
2
+ require "authlogic_rpx/acts_as_authentic"
3
+ require "authlogic_rpx/session"
4
+ require "authlogic_rpx/helper"
5
+ require "authlogic_rpx/rpx_identifier"
6
+
7
+ ActiveRecord::Base.send(:include, AuthlogicRpx::ActsAsAuthentic)
8
+ Authlogic::Session::Base.send(:include, AuthlogicRpx::Session)
9
+ ActionController::Base.helper AuthlogicRpx::Helper
@@ -0,0 +1,297 @@
1
+ # This module is responsible for adding RPX functionality to Authlogic. Checkout the README for more info and please
2
+ # see the sub modules for detailed documentation.
3
+ module AuthlogicRpx
4
+ # This module is responsible for adding in the RPX functionality to your models. It hooks itself into the
5
+ # acts_as_authentic method provided by Authlogic.
6
+ module ActsAsAuthentic
7
+ # Adds in the neccesary modules for acts_as_authentic to include and also disabled password validation if
8
+ # RPX is being used.
9
+ def self.included(klass)
10
+ klass.class_eval do
11
+ extend Config
12
+ add_acts_as_authentic_module(Methods, :prepend)
13
+ end
14
+ end
15
+
16
+ class GeneralError < StandardError
17
+ end
18
+ class ConfigurationError < StandardError
19
+ end
20
+
21
+ module Config
22
+
23
+ # account_merge_enabled is used to enable merging of accounts.
24
+ #
25
+ # * <tt>Default:</tt> false
26
+ # * <tt>Accepts:</tt> boolean
27
+ def account_merge_enabled(value=false)
28
+ account_merge_enabled_value(value)
29
+ end
30
+ def account_merge_enabled_value(value=nil)
31
+ rw_config(:account_merge_enabled,value,false)
32
+ end
33
+ alias_method :account_merge_enabled=,:account_merge_enabled
34
+
35
+ # account_mapping_mode is used to explicitly set/override the mapping behaviour.
36
+ #
37
+ # * <tt>Default:</tt> :auto
38
+ # * <tt>Accepts:</tt> :auto, :none, :internal, :rpxnow
39
+ def account_mapping_mode(value=:auto)
40
+ account_mapping_mode_value(value)
41
+ end
42
+ def account_mapping_mode_value(value=nil)
43
+ raise AuthlogicRpx::ActsAsAuthentic::ConfigurationError.new unless value.nil? || [:auto,:none,:internal].include?( value )
44
+ rw_config(:account_mapping_mode,value,:auto)
45
+ end
46
+ alias_method :account_mapping_mode=,:account_mapping_mode
47
+
48
+ # returns the actual account mapping mode in use - resolves :auto to actual mechanism
49
+ #
50
+ attr_writer :account_mapping_mode_used
51
+ def account_mapping_mode_used
52
+ @account_mapping_mode_used ||= (
53
+ account_mapping_mode_value == :auto ?
54
+ ( RPXIdentifier.table_exists? ?
55
+ :internal :
56
+ ( self.column_names.include?("rpx_identifier") ? :none : AuthlogicRpx::ActsAsAuthentic::ConfigurationError.new )
57
+ ) :
58
+ account_mapping_mode_value
59
+ )
60
+ end
61
+
62
+
63
+ # determines if no account mapping is supported (the only behaviour in authlogic_rpx v1.0.4)
64
+ def using_no_mapping?
65
+ account_mapping_mode_used == :none
66
+ end
67
+ # determines if internal account mapping is enabled (behaviour added in authlogic_rpx v1.1.0)
68
+ def using_internal_mapping?
69
+ account_mapping_mode_used == :internal
70
+ end
71
+ # determines if rpxnow account mapping is enabled (currently not implemented)
72
+ def using_rpx_mapping?
73
+ account_mapping_mode_used == :rpxnow
74
+ end
75
+
76
+ end
77
+
78
+ module Methods
79
+
80
+ # Mix-in the required methods based on mapping mode
81
+ #
82
+ def self.included(klass)
83
+ klass.class_eval do
84
+
85
+ case
86
+ when using_no_mapping?
87
+ include AuthlogicRpx::MethodSet_NoMapping
88
+
89
+ when using_internal_mapping?
90
+ include AuthlogicRpx::MethodSet_InternalMapping
91
+ has_many :rpx_identifiers, :class_name => 'RPXIdentifier', :dependent => :destroy
92
+
93
+ # Add custom find_by_rpx_identifier class method
94
+ #
95
+ def self.find_by_rpx_identifier(id)
96
+ identifier = RPXIdentifier.find_by_identifier(id)
97
+ if identifier.nil?
98
+ if self.column_names.include? 'rpx_identifier'
99
+ # check for authentication using <=1.0.4, migrate identifier to rpx_identifiers table
100
+ user = self.find( :first, :conditions => [ "rpx_identifier = ?", id ] )
101
+ unless user.nil?
102
+ user.add_rpx_identifier( id, 'Unknown' )
103
+ end
104
+ return user
105
+ else
106
+ return nil
107
+ end
108
+ else
109
+ identifier.send( self.methods.include?(:class_name) ? self.class_name.downcase : self.to_s.classify.downcase )
110
+ end
111
+ end
112
+
113
+ else
114
+ raise AuthlogicRpx::ActsAsAuthentic::ConfigurationError.new( "invalid or unsupported account_mapping_mode" )
115
+ end
116
+
117
+ # Set up some fundamental conditional validations
118
+ validates_length_of_password_field_options validates_length_of_password_field_options.merge(:if => :validate_password_not_rpx?)
119
+ validates_confirmation_of_password_field_options validates_confirmation_of_password_field_options.merge(:if => :validate_password_not_rpx?)
120
+ validates_length_of_password_confirmation_field_options validates_length_of_password_confirmation_field_options.merge(:if => :validate_password_not_rpx?)
121
+
122
+ before_validation :adding_rpx_identifier
123
+ end
124
+
125
+ # add relations and validation to RPXIdentifier based on the actual user model class name used
126
+ #
127
+ RPXIdentifier.class_eval do
128
+ belongs_to klass.name.downcase.to_sym
129
+ validates_presence_of "#{klass.name.downcase}_id".to_sym
130
+ end
131
+ end
132
+
133
+ # test if account it using normal password authentication
134
+ def using_password?
135
+ !send(crypted_password_field).blank?
136
+ end
137
+
138
+
139
+ private
140
+
141
+ # tests if password authentication should be checked instead of rpx (i.e. if rpx is enabled but not used by this user)
142
+ def validate_password_not_rpx?
143
+ !using_rpx? && require_password?
144
+ end
145
+
146
+ # determines if account merging is enabled; delegates to class method
147
+ def account_merge_enabled?
148
+ self.class.account_merge_enabled_value
149
+ end
150
+
151
+ # hook for adding RPX identifier to an existing account. This is invoked prior to model validation.
152
+ # RPX information is plucked from the controller session object (where it was placed by the session model as a result
153
+ # of the RPX callback)
154
+ # The minimal action taken is to add an RPXIdentifier object to the user.
155
+ #
156
+ # This procedure chains to the map_added_rpx_data, which may be over-ridden in your project to perform
157
+ # additional mapping of RPX information to the user model as may be desired.
158
+ #
159
+ def adding_rpx_identifier
160
+ return true unless session_class && session_class.controller
161
+
162
+ added_rpx_data = session_class.controller.session['added_rpx_data']
163
+ unless added_rpx_data.blank?
164
+ session_class.controller.session['added_rpx_data'] = nil
165
+ rpx_id = added_rpx_data['profile']['identifier']
166
+ rpx_provider_name = added_rpx_data['profile']['providerName']
167
+
168
+ unless self.identified_by?( rpx_id )
169
+ # identifier not already set for this user..
170
+
171
+ another_user = self.class.find_by_rpx_identifier( rpx_id )
172
+ if another_user
173
+ return false unless account_merge_enabled?
174
+ # another user already has this id registered..
175
+
176
+ # merge all IDs from another_user to self, with application callbacks before/after
177
+ before_merge_rpx_data( another_user, self )
178
+ merge_user_id another_user
179
+ after_merge_rpx_data( another_user, self )
180
+
181
+ else
182
+ self.add_rpx_identifier( rpx_id, rpx_provider_name )
183
+ end
184
+ end
185
+
186
+ map_added_rpx_data( added_rpx_data )
187
+ end
188
+ end
189
+
190
+
191
+ # map_added_rpx_data maps additional fields from the RPX response into the user object during the "add RPX to existing account" process.
192
+ # Override this in your user model to perform field mapping as may be desired
193
+ # See https://rpxnow.com/docs#profile_data for the definition of available attributes
194
+ #
195
+ # "self" at this point is the user model. Map details as appropriate from the rpx_data structure provided.
196
+ #
197
+ def map_added_rpx_data( rpx_data )
198
+
199
+ end
200
+
201
+ # before_merge_rpx_data provides a hook for application developers to perform data migration prior to the merging of user accounts.
202
+ # This method is called just before authlogic_rpx merges the user registration for 'from_user' into 'to_user'
203
+ # Authlogic_RPX is responsible for merging registration data.
204
+ #
205
+ # By default, it does not merge any other details (e.g. application data ownership)
206
+ #
207
+ def before_merge_rpx_data( from_user, to_user )
208
+
209
+ end
210
+
211
+ # after_merge_rpx_data provides a hook for application developers to perform account clean-up after authlogic_rpx has
212
+ # migrated registration details.
213
+ #
214
+ # By default, does nothing. It could, for example, be used to delete or disable the 'from_user' account
215
+ #
216
+ def after_merge_rpx_data( from_user, to_user )
217
+
218
+ end
219
+
220
+
221
+ end
222
+ end
223
+
224
+ # Mix-in collection of methods that are specific to no-mapping mode of operation
225
+ #
226
+ module MethodSet_NoMapping
227
+ # test if account it using RPX authentication
228
+ #
229
+ def using_rpx?
230
+ !rpx_identifier.blank?
231
+ end
232
+
233
+ # adds RPX identification to the instance.
234
+ # Abstracts how the RPX identifier is added to allow for multiplicity of underlying implementations
235
+ #
236
+ def add_rpx_identifier( rpx_id, rpx_provider_name )
237
+ self.rpx_identifier = rpx_id
238
+ #TODO: make rpx_provider_name a std param?
239
+ end
240
+
241
+ # Checks if given identifier is an identity for this account
242
+ #
243
+ def identified_by?( id )
244
+ self.rpx_identifier == id
245
+ end
246
+
247
+ # merge_user_id is an internal method used to merge the actual RPX identifiers
248
+ #
249
+ def merge_user_id( from_user )
250
+ self.rpx_identifier = from_user.rpx_identifier
251
+ from_user.rpx_identifier = nil
252
+ from_user.save
253
+ from_user.reload
254
+ end
255
+
256
+ # Uses default find_by_rpx_identifier class method
257
+
258
+ # Add an rpx_identifier collection method
259
+ def rpx_identifiers
260
+ [{ :identifier => rpx_identifier, :provider_name => "Unknown" }]
261
+ end
262
+ end
263
+
264
+
265
+ # Mix-in collection of methods that are specific to internal mapping mode of operation
266
+ #
267
+ module MethodSet_InternalMapping
268
+ # test if account it using RPX authentication
269
+ #
270
+ def using_rpx?
271
+ !rpx_identifiers.empty?
272
+ end
273
+
274
+ # adds RPX identification to the instance.
275
+ # Abstracts how the RPX identifier is added to allow for multiplicity of underlying implementations
276
+ #
277
+ def add_rpx_identifier( rpx_id, rpx_provider_name )
278
+ self.rpx_identifiers.build(:identifier => rpx_id, :provider_name => rpx_provider_name )
279
+ end
280
+
281
+ # Checks if given identifier is an identity for this account
282
+ #
283
+ def identified_by?( id )
284
+ self.rpx_identifiers.find_by_identifier( id )
285
+ end
286
+
287
+ # merge_user_id is an internal method used to merge the actual RPX identifiers
288
+ #
289
+ def merge_user_id( from_user )
290
+ self.rpx_identifiers << from_user.rpx_identifiers
291
+ from_user.reload
292
+ end
293
+
294
+
295
+ end
296
+
297
+ end