slayer-authlogic_rpx 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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