Empact-authlogic_rpx 1.1.2

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 (40) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.rdoc +39 -0
  3. data/Empact-authlogic_rpx.gemspec +100 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +747 -0
  6. data/Rakefile +47 -0
  7. data/VERSION +1 -0
  8. data/generators/add_authlogic_rpx_migration/USAGE +18 -0
  9. data/generators/add_authlogic_rpx_migration/add_authlogic_rpx_migration_generator.rb +44 -0
  10. data/generators/add_authlogic_rpx_migration/templates/migration_internal_mapping.rb +34 -0
  11. data/generators/add_authlogic_rpx_migration/templates/migration_no_mapping.rb +29 -0
  12. data/init.rb +1 -0
  13. data/lib/authlogic_rpx.rb +13 -0
  14. data/lib/authlogic_rpx/acts_as_authentic.rb +274 -0
  15. data/lib/authlogic_rpx/helper.rb +44 -0
  16. data/lib/authlogic_rpx/rpx_identifier.rb +5 -0
  17. data/lib/authlogic_rpx/session.rb +241 -0
  18. data/lib/authlogic_rpx/session/validation.rb +30 -0
  19. data/lib/authlogic_rpx/version.rb +51 -0
  20. data/rails/init.rb +1 -0
  21. data/test/fixtures/rpxresponses.yml +20 -0
  22. data/test/fixtures/users.yml +20 -0
  23. data/test/integration/basic_authentication_and_registration_test.rb +73 -0
  24. data/test/integration/internal_mapping/basic_authentication_and_registration_test.rb +3 -0
  25. data/test/integration/internal_mapping/settings_test.rb +10 -0
  26. data/test/integration/no_mapping/basic_authentication_and_registration_test.rb +3 -0
  27. data/test/integration/no_mapping/settings_test.rb +10 -0
  28. data/test/libs/ext_test_unit.rb +30 -0
  29. data/test/libs/mock_rpx_now.rb +34 -0
  30. data/test/libs/rails_trickery.rb +41 -0
  31. data/test/libs/rpxresponse.rb +3 -0
  32. data/test/libs/user.rb +3 -0
  33. data/test/libs/user_session.rb +3 -0
  34. data/test/test_helper.rb +85 -0
  35. data/test/test_internal_mapping_helper.rb +93 -0
  36. data/test/unit/acts_as_authentic_settings_test.rb +42 -0
  37. data/test/unit/session_settings_test.rb +38 -0
  38. data/test/unit/session_validation_test.rb +16 -0
  39. data/test/unit/verify_rpx_mock_test.rb +29 -0
  40. metadata +143 -0
@@ -0,0 +1,44 @@
1
+ module AuthlogicRpx
2
+ module Helper
3
+
4
+ # helper to insert an embedded iframe RPX login
5
+ # takes options hash:
6
+ # * <tt>app_name:</tt> name of the application (will be prepended to RPX domain and used in RPX dialogues)
7
+ # * <tt>return_url:</tt> url for the RPX callback (e.g. user_sessions_url)
8
+ # * <tt>add_rpx:</tt> if true, requests RPX callback to add to current session. Else runs normal authentication process (default)
9
+ #
10
+ # The options hash may include other options as supported by rpx_now (see http://github.com/grosser/rpx_now)
11
+ #
12
+ def rpx_embed(options = {})
13
+ app_name = options.delete( :app_name )
14
+ token_url = build_token_url!( options )
15
+ RPXNow.embed_code(app_name, token_url, options )
16
+ end
17
+
18
+ # helper to insert a link to pop-up RPX login
19
+ # takes options hash:
20
+ # * <tt>link_text:</tt> text to use in the link
21
+ # * <tt>app_name:</tt> name of the application (will be prepended to RPX domain and used in RPX dialogues)
22
+ # * <tt>return_url:</tt> url for the RPX callback (e.g. user_sessions_url)
23
+ # * <tt>add_rpx:</tt> if true, requests RPX callback to add to current session. Else runs normal authentication process (default)
24
+ # * <tt>unobtrusive:</tt> true/false; sets javascript style for link. Default: true
25
+ #
26
+ # The options hash may include other options as supported by rpx_now (see http://github.com/grosser/rpx_now)
27
+ #
28
+ def rpx_popup(options = {})
29
+ options = { :unobtrusive => true, :add_rpx => false }.merge( options )
30
+ app_name = options.delete( :app_name )
31
+ link_text = options.delete( :link_text )
32
+ token_url = build_token_url!( options )
33
+ RPXNow.popup_code( link_text, app_name, token_url, options )
34
+ end
35
+
36
+ private
37
+
38
+ def build_token_url!( options )
39
+ options.delete( :return_url ) + '?' + (
40
+ { :authenticity_token => form_authenticity_token, :add_rpx => options.delete( :add_rpx ) }.collect { |n| "#{n[0]}=#{ u(n[1]) }" if n[1] }
41
+ ).compact.join('&')
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ class RPXIdentifier < ActiveRecord::Base
2
+ validates_presence_of :identifier
3
+ validates_uniqueness_of :identifier
4
+ validates_presence_of :user_id
5
+ end
@@ -0,0 +1,241 @@
1
+ module AuthlogicRpx
2
+ # This module is responsible for adding all of the RPX goodness to the Authlogic::Session::Base class.
3
+ module Session
4
+ # Add a simple rpx_identifier attribute and some validations for the field.
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend Config
8
+ include Methods
9
+ end
10
+ end
11
+
12
+ module Config
13
+
14
+ def find_by_rpx_identifier_method(value = nil)
15
+ rw_config(:find_by_rpx_identifier_method, value, :find_by_rpx_identifier)
16
+ end
17
+ alias_method :find_by_rpx_identifier_method=, :find_by_rpx_identifier_method
18
+
19
+ # Auto Register is enabled by default.
20
+ # Add this in your Session object if you need to disable auto-registration via rpx
21
+ #
22
+ def auto_register(value=true)
23
+ auto_register_value(value)
24
+ end
25
+ def auto_register_value(value=nil)
26
+ rw_config(:auto_register,value,true)
27
+ end
28
+ alias_method :auto_register=,:auto_register
29
+
30
+ # Add this in your Session object to set the RPX API key
31
+ # RPX won't work without the API key. Set it here if not already set in your app configuration.
32
+ #
33
+ def rpx_key(value=nil)
34
+ rpx_key_value(value)
35
+ end
36
+ def rpx_key_value(value=nil)
37
+ if !inheritable_attributes.include?(:rpx_key)
38
+ RPXNow.api_key = value
39
+ end
40
+ rw_config(:rpx_key,value,false)
41
+ end
42
+ alias_method :rpx_key=,:rpx_key
43
+
44
+ # Add this in your Session object to set whether RPX returns extended user info
45
+ # By default, it will not, which is enough to get username, name, email and the rpx identified
46
+ # if you want to map additional information into your user details, you can request extended
47
+ # attributes (though not all providers give them - see the RPX docs)
48
+ #
49
+ def rpx_extended_info(value=true)
50
+ rpx_extended_info_value(value)
51
+ end
52
+ def rpx_extended_info_value(value=nil)
53
+ rw_config(:rpx_extended_info,value,false)
54
+ end
55
+ alias_method :rpx_extended_info=,:rpx_extended_info
56
+
57
+ end
58
+
59
+ module Methods
60
+
61
+ def self.included(klass)
62
+ klass.class_eval do
63
+ attr_accessor :new_registration
64
+ after_persisting :add_rpx_identifier, :if => :adding_rpx_identifier?
65
+ validate :validate_by_rpx, :if => :authenticating_with_rpx?
66
+ validate :validate_user
67
+ end
68
+ end
69
+
70
+ # Determines if the authenticated user is also a new registration.
71
+ # For use in the session controller to help direct the most appropriate action to follow.
72
+ #
73
+ def new_registration?
74
+ new_registration || !new_registration.nil?
75
+ end
76
+
77
+ # True if the login was succeessful, but the logged-in user object is invalid.
78
+ # For use in the session controller to help direct the most appropriate action to follow.
79
+ #
80
+ def registration_incomplete?
81
+ valid? && attempted_record && !attempted_record.valid?
82
+ end
83
+
84
+ # Determines if the authenticated user has a complete registration (no validation errors)
85
+ # For use in the session controller to help direct the most appropriate action to follow.
86
+ #
87
+ def registration_complete?
88
+ attempted_record && attempted_record.valid?
89
+ end
90
+
91
+ private
92
+ # Tests if current request is for RPX authentication
93
+ #
94
+ def authenticating_with_rpx?
95
+ controller.params[:token] && !controller.params[:add_rpx]
96
+ end
97
+
98
+ # hook instance finder method to class
99
+ #
100
+ def find_by_rpx_identifier_method
101
+ self.class.find_by_rpx_identifier_method
102
+ end
103
+
104
+ # Tests if auto_registration is enabled (on by default)
105
+ #
106
+ def auto_register?
107
+ self.class.auto_register_value
108
+ end
109
+
110
+ # Tests if rpx_extended_info is enabled (off by default)
111
+ #
112
+ def rpx_extended_info?
113
+ self.class.rpx_extended_info_value
114
+ end
115
+
116
+ # Tests if current request is the special case of adding RPX to an existing account
117
+ #
118
+ def adding_rpx_identifier?
119
+ controller.params[:token] && controller.params[:add_rpx]
120
+ end
121
+
122
+ # Handles the special case of RPX being added to an existing account.
123
+ # At this point, a session has been established as a result of a "save" on the user model (which indirectly triggers user session validation).
124
+ # We do not directly add the RPX details to the user record here in order to avoid getting
125
+ # into a recursive dance between the session and user models.
126
+ # Rather, it uses the trick of adding the necessary RPX information to the session object,
127
+ # and the user model will pluck these values out before completing its validation step.
128
+ #
129
+ def add_rpx_identifier
130
+ data = RPXNow.user_data(controller.params[:token], :extended=> rpx_extended_info? ) {|raw| raw }
131
+ controller.session['added_rpx_data'] = data if data
132
+ end
133
+
134
+ # the main RPX magic. At this point, a session is being validated and we know RPX identifier
135
+ # has been provided. We'll callback to RPX to verify the token, and authenticate the matching
136
+ # user.
137
+ # If no user is found, and we have auto_register enabled (default) this method will also
138
+ # create the user registration stub.
139
+ #
140
+ # On return to the controller, you can test for new_registration? and registration_complete?
141
+ # to determine the most appropriate action
142
+ #
143
+ def validate_by_rpx
144
+ @rpx_data = RPXNow.user_data(
145
+ controller.params[:token],
146
+ :extended => rpx_extended_info?) { |raw| raw }
147
+
148
+ # If we don't have a valid sign-in, give-up at this point
149
+ if @rpx_data.nil? || @rpx_data['profile'].nil?
150
+ errors.add_to_base("Authentication failed. Please try again.")
151
+ return false
152
+ end
153
+
154
+ rpx_id = @rpx_data['profile']['identifier']
155
+ rpx_provider_name = @rpx_data['profile']['providerName']
156
+ if rpx_id.blank?
157
+ errors.add_to_base("Authentication failed. Please try again.")
158
+ return false
159
+ end
160
+
161
+ self.attempted_record = klass.send(find_by_rpx_identifier_method, rpx_id)
162
+
163
+ # so what do we do if we can't find an existing user matching the RPX authentication...
164
+ if !attempted_record
165
+ if auto_register?
166
+ self.attempted_record = new_rpx_user(controller.params)
167
+ map_rpx_data
168
+
169
+ # save the new user record - without session maintenance else we
170
+ # get caught in a self-referential hell, since both session and
171
+ # user objects invoke each other upon save
172
+ self.new_registration = true
173
+ self.attempted_record.add_rpx_identifier( rpx_id, rpx_provider_name)
174
+ self.attempted_record.save_without_session_maintenance
175
+ else
176
+ errors.add_to_base("We did not find any accounts with that login. Enter your details and create an account.")
177
+ return false
178
+ end
179
+ else
180
+ map_rpx_data_each_login
181
+ end
182
+
183
+ end
184
+
185
+ def validate_user
186
+ errors.add(:user, "is invalid") if attempted_record && !attempted_record.valid?
187
+ end
188
+
189
+ # map_rpx_data maps additional fields from the RPX response into the user object during auto-registration.
190
+ # Override this in your session model to change the field mapping
191
+ # See https://rpxnow.com/docs#profile_data for the definition of available attributes
192
+ #
193
+ # In this procedure, you will be writing to fields of the "self.attempted_record" object, pulling data from the @rpx_data object.
194
+ #
195
+ # WARNING: if you are using auto-registration, any fields you map should NOT have constraints enforced at the database level.
196
+ # authlogic_rpx will optimistically attempt to save the user record during registration, and
197
+ # violating a database constraint will cause the authentication/registration to fail.
198
+ #
199
+ # You can/should enforce any required validations at the model level e.g.
200
+ # validates_uniqueness_of :username, :case_sensitive => false
201
+ # This will allow the auto-registration to proceed, and the user can be given a chance to rectify the validation errors
202
+ # on your user profile page.
203
+ #
204
+ # If it is not acceptable in your application to have user records created with potential validation errors in auto-populated fields, you
205
+ # will need to override map_rpx_data and implement whatever special handling makes sense in your case. For example:
206
+ # - directly check for uniqueness and other validation requirements
207
+ # - automatically "uniquify" fields like username
208
+ # - save conflicting profile information to "pending user review" columns or a seperate table
209
+ #
210
+ def map_rpx_data
211
+ self.attempted_record.send("#{klass.login_field}=", @rpx_data['profile']['preferredUsername'] ) if attempted_record.send(klass.login_field).blank?
212
+ self.attempted_record.send("#{klass.email_field}=", @rpx_data['profile']['email'] ) if attempted_record.send(klass.email_field).blank?
213
+ end
214
+
215
+ # new_rpx_user creates a fresh user in the case of auto-registration, prior to mapping in
216
+ # the rpx data via map_rpx_data. You are passed the current controller params,
217
+ # which you can use to initialize the user.
218
+ #
219
+ # Override this in your session model to change the initialization of auto-registered users.
220
+ #
221
+ def new_rpx_user(params)
222
+ klass.new()
223
+ end
224
+
225
+ # map_rpx_data_each_login provides a hook to allow you to map RPX profile information every time the user
226
+ # logs in.
227
+ # By default, nothing is mapped.
228
+ #
229
+ # This would mainly be used to update relatively volatile information that you are maintaining in the user model (such as profile image url)
230
+ #
231
+ # In this procedure, you will be writing to fields of the "self.attempted_record" object, pulling data from the @rpx_data object.
232
+ #
233
+ #
234
+ def map_rpx_data_each_login
235
+
236
+ end
237
+
238
+ end
239
+
240
+ end
241
+ end
@@ -0,0 +1,30 @@
1
+ module AuthlogicRpx
2
+ module Session # :nodoc:
3
+ # This is different from mainline Session::Base#valid? in that it does not
4
+ # attempt to save_record on the attempted_record
5
+ #
6
+ # In a world without auto-registration the save, which seems to have been a
7
+ # performance optimization (authlogic SHA: 198521b75d26143c43), would be unlikely
8
+ # to produce show-stopper errors, but as RPX-driven auto-registration is
9
+ # far less likely to generate valid objects (for example, they can easily violate uniqueness
10
+ # constraints/validations), it is important not to save to avoid blowing up in these cases.
11
+ module Validation
12
+ def valid?
13
+ errors.clear
14
+ self.attempted_record = nil
15
+
16
+ before_validation
17
+ new_session? ? before_validation_on_create : before_validation_on_update
18
+ validate
19
+ ensure_authentication_attempted
20
+
21
+ if errors.size == 0
22
+ new_session? ? after_validation_on_create : after_validation_on_update
23
+ after_validation
24
+ end
25
+
26
+ errors.size == 0
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ module AuthlogicRpx
2
+ # A class for describing the current version of a library. The version
3
+ # consists of three parts: the +major+ number, the +minor+ number, and the
4
+ # +tiny+ (or +patch+) number.
5
+ class Version
6
+ include Comparable
7
+
8
+ # A convenience method for instantiating a new Version instance with the
9
+ # given +major+, +minor+, and +tiny+ components.
10
+ def self.[](major, minor, tiny)
11
+ new(major, minor, tiny)
12
+ end
13
+
14
+ attr_reader :major, :minor, :tiny
15
+
16
+ # Create a new Version object with the given components.
17
+ def initialize(major, minor, tiny)
18
+ @major, @minor, @tiny = major, minor, tiny
19
+ end
20
+
21
+ # Compare this version to the given +version+ object.
22
+ def <=>(version)
23
+ to_i <=> version.to_i
24
+ end
25
+
26
+ # Converts this version object to a string, where each of the three
27
+ # version components are joined by the '.' character. E.g., 2.0.0.
28
+ def to_s
29
+ @to_s ||= [@major, @minor, @tiny].join(".")
30
+ end
31
+
32
+ # Converts this version to a canonical integer that may be compared
33
+ # against other version objects.
34
+ def to_i
35
+ @to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
36
+ end
37
+
38
+ def to_a
39
+ [@major, @minor, @tiny]
40
+ end
41
+
42
+ MAJOR = 1
43
+ MINOR = 1
44
+ TINY = 1
45
+
46
+ # The current version as a Version instance
47
+ CURRENT = new(MAJOR, MINOR, TINY)
48
+ # The current version as a String
49
+ STRING = CURRENT.to_s
50
+ end
51
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "authlogic_rpx"
@@ -0,0 +1,20 @@
1
+ valid_rpx_auth_user_one:
2
+ identifier : http://provider.one/valid_rpx_auth_user_one
3
+ provider_name: provider.one
4
+ username: valid_rpx_auth_user_one
5
+ verified_email: valid_rpx_auth_user_one@provider.one
6
+ display_name: valid rpx auth user one
7
+
8
+ valid_rpx_auth_user_two:
9
+ identifier : http://provider.one/valid_rpx_auth_user_two
10
+ provider_name: provider.one
11
+ username: valid_rpx_auth_user_two
12
+ verified_email: valid_rpx_auth_user_two@provider.one
13
+ display_name: valid rpx auth user two
14
+
15
+ unregistered_rpx_auth_user_one:
16
+ identifier : http://provider.one/unregistered_rpx_auth_user_one
17
+ provider_name: provider.one
18
+ username: unregistered_rpx_auth_user_one
19
+ verified_email: unregistered_rpx_auth_user_one@provider.one
20
+ display_name: unregistered rpx auth user one
@@ -0,0 +1,20 @@
1
+ valid_rpx_auth_user_one:
2
+ login: valid_rpx_auth_user_one
3
+ email: valid_rpx_auth_user_one@provider.one
4
+ persistence_token: 6cde0674657a8a313ce952df979de2830309aa4c11ca65805dd00bfdc65dbcc2f5e36718660a1d2e68c1a08c276d996763985d2f06fd3d076eb7bc4d97b1e317
5
+ single_access_token: <%= Authlogic::Random.friendly_token %>
6
+ perishable_token: <%= Authlogic::Random.friendly_token %>
7
+ rpx_identifier : http://provider.one/valid_rpx_auth_user_one
8
+
9
+ valid_rpx_auth_user_two:
10
+ login: valid_rpx_auth_user_two
11
+ email: valid_rpx_auth_user_two@provider.one
12
+ persistence_token: 6cde0674657a8a313ce952df979de2830309aa4c11ca65805dd00bfdc65dbcc2f5e36718660a1d2e68c1a08c276d996763985d2f06fd3d076eb7bc4d97b1e317
13
+ single_access_token: <%= Authlogic::Random.friendly_token %>
14
+ perishable_token: <%= Authlogic::Random.friendly_token %>
15
+ rpx_identifier : http://provider.one/valid_rpx_auth_user_two
16
+
17
+ invalid_rpx_auth_user_one:
18
+ login: invalid_rpx_auth_user_one
19
+
20
+
@@ -0,0 +1,73 @@
1
+ # requires test_helper to be loaded first
2
+
3
+ class BasicAuthenticationAndRegistrationTest < ActiveSupport::TestCase
4
+
5
+ must "authenticate valid existing user" do
6
+ test_user = users(:valid_rpx_auth_user_one)
7
+ controller.params[:token] = test_user.login
8
+ session = UserSession.new
9
+ assert_true session.save, "should be a valid session"
10
+ assert_false session.new_registration?, "should not be a new registration"
11
+ assert_true session.registration_complete?, "registration should be complete"
12
+ assert_equal test_user, session.record
13
+ end
14
+
15
+ must "do not authenticate invalidate non-existent user" do
16
+ controller.params[:token] = ''
17
+ session = UserSession.new
18
+ assert_false session.save, "should not be a valid session"
19
+ end
20
+
21
+ must "mark an invalaid auto-registered user as registration_incomplete" do
22
+ # enforce Authlogic settings required for test
23
+ UserSession.auto_register true
24
+ User.account_merge_enabled false
25
+ User.account_mapping_mode :none
26
+
27
+ # get response template. set the controller token (used by RPX mock to match mock response)
28
+ test_user = rpxresponses(:unregistered_rpx_auth_user_one)
29
+ controller.params[:token] = test_user.username
30
+ # emulate constraint violation
31
+
32
+ session = UserSession.new
33
+
34
+ stub.instance_of(User).save_without_session_maintenance.with_any_args {|*args|
35
+ raise "Kaboom" unless args.empty? || args.first
36
+ }
37
+ stub.instance_of(User).valid? { false }
38
+ assert_true session.registration_incomplete?
39
+ end
40
+
41
+ must "auto-register an unregistered user" do
42
+ # enforce Authlogic settings required for test
43
+ UserSession.auto_register true
44
+ User.account_merge_enabled false
45
+ User.account_mapping_mode :none
46
+
47
+ # get response template. set the controller token (used by RPX mock to match mock response)
48
+ test_user = rpxresponses(:unregistered_rpx_auth_user_one)
49
+ controller.params[:token] = test_user.username
50
+
51
+ session = UserSession.new
52
+ assert_false session.registration_incomplete?
53
+ assert_true session.save, "should be a valid session"
54
+ assert_true session.new_registration?, "should be a new registration"
55
+ assert_true session.registration_complete?, "registration should be complete"
56
+ end
57
+
58
+ must "auto-register disabled for an unregistered user" do
59
+ # enforce Authlogic settings required for test
60
+ UserSession.auto_register false
61
+ User.account_merge_enabled false
62
+ User.account_mapping_mode :none
63
+
64
+ # get response template. set the controller token (used by RPX mock to match mock response)
65
+ test_user = rpxresponses(:unregistered_rpx_auth_user_one)
66
+ controller.params[:token] = test_user.username
67
+
68
+ session = UserSession.new
69
+ assert_false session.registration_incomplete?
70
+ assert_false session.save, "should not be a valid session"
71
+ end
72
+
73
+ end