authlogic 1.4.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of authlogic might be problematic. Click here for more details.

Files changed (131) hide show
  1. data/CHANGELOG.rdoc +19 -0
  2. data/Manifest.txt +111 -0
  3. data/README.rdoc +116 -389
  4. data/Rakefile +14 -7
  5. data/lib/authlogic.rb +33 -35
  6. data/lib/authlogic/acts_as_authentic/base.rb +91 -0
  7. data/lib/authlogic/acts_as_authentic/email.rb +77 -0
  8. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +54 -0
  9. data/lib/authlogic/acts_as_authentic/login.rb +65 -0
  10. data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  11. data/lib/authlogic/acts_as_authentic/password.rb +215 -0
  12. data/lib/authlogic/acts_as_authentic/perishable_token.rb +100 -0
  13. data/lib/authlogic/acts_as_authentic/persistence_token.rb +66 -0
  14. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +60 -0
  15. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +127 -0
  16. data/lib/authlogic/acts_as_authentic/single_access_token.rb +58 -0
  17. data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  18. data/lib/authlogic/{session/authenticates_many_association.rb → authenticates_many/association.rb} +10 -6
  19. data/lib/authlogic/authenticates_many/base.rb +55 -0
  20. data/lib/authlogic/controller_adapters/abstract_adapter.rb +2 -3
  21. data/lib/authlogic/controller_adapters/merb_adapter.rb +0 -4
  22. data/lib/authlogic/controller_adapters/rails_adapter.rb +0 -4
  23. data/lib/authlogic/crypto_providers/aes256.rb +0 -2
  24. data/lib/authlogic/crypto_providers/bcrypt.rb +0 -2
  25. data/lib/authlogic/crypto_providers/md5.rb +34 -0
  26. data/lib/authlogic/crypto_providers/sha1.rb +0 -2
  27. data/lib/authlogic/crypto_providers/sha512.rb +1 -3
  28. data/lib/authlogic/i18n.rb +1 -4
  29. data/lib/authlogic/random.rb +33 -0
  30. data/lib/authlogic/session/activation.rb +56 -0
  31. data/lib/authlogic/session/active_record_trickery.rb +15 -7
  32. data/lib/authlogic/session/base.rb +31 -456
  33. data/lib/authlogic/session/brute_force_protection.rb +50 -27
  34. data/lib/authlogic/session/callbacks.rb +24 -15
  35. data/lib/authlogic/session/cookies.rb +108 -22
  36. data/lib/authlogic/session/existence.rb +89 -0
  37. data/lib/authlogic/session/foundation.rb +63 -0
  38. data/lib/authlogic/session/http_auth.rb +23 -0
  39. data/lib/authlogic/session/id.rb +41 -0
  40. data/lib/authlogic/session/klass.rb +75 -0
  41. data/lib/authlogic/session/magic_columns.rb +75 -0
  42. data/lib/authlogic/session/magic_states.rb +58 -0
  43. data/lib/authlogic/session/params.rb +82 -19
  44. data/lib/authlogic/session/password.rb +156 -0
  45. data/lib/authlogic/session/{perishability.rb → perishable_token.rb} +4 -4
  46. data/lib/authlogic/session/persistence.rb +70 -0
  47. data/lib/authlogic/session/priority_record.rb +34 -0
  48. data/lib/authlogic/session/scopes.rb +57 -53
  49. data/lib/authlogic/session/session.rb +46 -31
  50. data/lib/authlogic/session/timeout.rb +65 -31
  51. data/lib/authlogic/session/unauthorized_record.rb +50 -0
  52. data/lib/authlogic/session/validation.rb +76 -0
  53. data/lib/authlogic/testing/test_unit_helpers.rb +3 -3
  54. data/lib/authlogic/version.rb +3 -3
  55. data/test/acts_as_authentic_test/base_test.rb +12 -0
  56. data/test/acts_as_authentic_test/email_test.rb +79 -0
  57. data/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  58. data/test/acts_as_authentic_test/login_test.rb +79 -0
  59. data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  60. data/test/acts_as_authentic_test/password_test.rb +212 -0
  61. data/test/acts_as_authentic_test/perishable_token_test.rb +56 -0
  62. data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  63. data/test/acts_as_authentic_test/session_maintenance_test.rb +68 -0
  64. data/test/acts_as_authentic_test/single_access_test.rb +39 -0
  65. data/test/authenticates_many_test.rb +16 -0
  66. data/test/{crypto_provider_tests → crypto_provider_test}/aes256_test.rb +1 -1
  67. data/test/{crypto_provider_tests → crypto_provider_test}/bcrypt_test.rb +1 -1
  68. data/test/{crypto_provider_tests → crypto_provider_test}/sha1_test.rb +1 -1
  69. data/test/{crypto_provider_tests → crypto_provider_test}/sha512_test.rb +1 -1
  70. data/test/fixtures/employees.yml +4 -4
  71. data/test/fixtures/users.yml +6 -6
  72. data/test/libs/company.rb +6 -0
  73. data/test/libs/employee.rb +7 -0
  74. data/test/libs/employee_session.rb +2 -0
  75. data/test/libs/project.rb +3 -0
  76. data/test/libs/user_session.rb +2 -0
  77. data/test/random_test.rb +49 -0
  78. data/test/session_test/activation_test.rb +43 -0
  79. data/test/session_test/active_record_trickery_test.rb +26 -0
  80. data/test/session_test/brute_force_protection_test.rb +76 -0
  81. data/test/session_test/callbacks_test.rb +6 -0
  82. data/test/session_test/cookies_test.rb +107 -0
  83. data/test/session_test/credentials_test.rb +0 -0
  84. data/test/session_test/existence_test.rb +64 -0
  85. data/test/session_test/http_auth_test.rb +16 -0
  86. data/test/session_test/id_test.rb +17 -0
  87. data/test/session_test/klass_test.rb +35 -0
  88. data/test/session_test/magic_columns_test.rb +59 -0
  89. data/test/session_test/magic_states_test.rb +60 -0
  90. data/test/session_test/params_test.rb +53 -0
  91. data/test/session_test/password_test.rb +84 -0
  92. data/test/{session_tests → session_test}/perishability_test.rb +1 -1
  93. data/test/session_test/persistence_test.rb +21 -0
  94. data/test/{session_tests → session_test}/scopes_test.rb +2 -3
  95. data/test/session_test/session_test.rb +59 -0
  96. data/test/session_test/timeout_test.rb +43 -0
  97. data/test/session_test/unauthorized_record_test.rb +13 -0
  98. data/test/session_test/validation_test.rb +23 -0
  99. data/test/test_helper.rb +14 -29
  100. metadata +120 -112
  101. data/Manifest +0 -76
  102. data/authlogic.gemspec +0 -38
  103. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/base.rb +0 -22
  104. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb +0 -238
  105. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb +0 -155
  106. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb +0 -51
  107. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/perishability.rb +0 -71
  108. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb +0 -94
  109. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb +0 -87
  110. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/single_access.rb +0 -61
  111. data/lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb +0 -58
  112. data/lib/authlogic/session/config.rb +0 -421
  113. data/lib/authlogic/session/errors.rb +0 -18
  114. data/lib/authlogic/session/record_info.rb +0 -24
  115. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb +0 -154
  116. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/credentials_test.rb +0 -157
  117. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/logged_in_test.rb +0 -24
  118. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/perishability_test.rb +0 -41
  119. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/persistence_test.rb +0 -54
  120. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/session_maintenance_test.rb +0 -62
  121. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/single_access_test.rb +0 -41
  122. data/test/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb +0 -32
  123. data/test/session_tests/active_record_trickery_test.rb +0 -14
  124. data/test/session_tests/authenticates_many_association_test.rb +0 -28
  125. data/test/session_tests/base_test.rb +0 -307
  126. data/test/session_tests/brute_force_protection_test.rb +0 -53
  127. data/test/session_tests/config_test.rb +0 -184
  128. data/test/session_tests/cookies_test.rb +0 -32
  129. data/test/session_tests/params_test.rb +0 -32
  130. data/test/session_tests/session_test.rb +0 -45
  131. data/test/session_tests/timeout_test.rb +0 -71
@@ -1,15 +1,19 @@
1
1
  module Authlogic
2
- module Session
3
- # = Authenticates Many Association
4
- #
5
- # An object of this class is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details and call them on an object, which allows you to do the following:
2
+ module AuthenticatesMany
3
+ # An object of this class is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details
4
+ # and call them on an object, which allows you to do the following:
6
5
  #
7
6
  # @account.user_sessions.new
8
7
  # @account.user_sessions.find
9
8
  # # ... etc
10
9
  #
11
- # You can call all of the class level methods off of an object with a saved scope, so that calling the above methods scopes the user sessions down to that specific account.
12
- class AuthenticatesManyAssociation # :nodoc:
10
+ # You can call all of the class level methods off of an object with a saved scope, so that calling the above methods scopes the user
11
+ # sessions down to that specific account. To implement this via ActiveRecord do something like:
12
+ #
13
+ # class User < ActiveRecord::Base
14
+ # authenticates_many :user_sessions
15
+ # end
16
+ class Association
13
17
  attr_accessor :klass, :find_options, :id
14
18
 
15
19
  def initialize(klass, find_options, id)
@@ -0,0 +1,55 @@
1
+ module Authlogic
2
+ # This allows you to scope your authentication. For example, let's say all users belong to an account, you want to make sure only users
3
+ # that belong to that account can actually login into that account. Simple, just do:
4
+ #
5
+ # class Account < ActiveRecord::Base
6
+ # authenticates_many :user_sessions
7
+ # end
8
+ #
9
+ # Now you can scope sessions just like everything else in ActiveRecord:
10
+ #
11
+ # @account.user_sessions.new(*args)
12
+ # @account.user_sessions.create(*args)
13
+ # @account.user_sessions.find(*args)
14
+ # # ... etc
15
+ #
16
+ # Checkout the authenticates_many method for a list of options.
17
+ # You may also want to checkout Authlogic::ActsAsAuthentic::Scope to scope your model.
18
+ module AuthenticatesMany
19
+ module Base
20
+ # Allows you set essentially set up a relationship with your sessions. See module definition above for more details.
21
+ #
22
+ # === Options
23
+ #
24
+ # * <tt>session_class:</tt> default: "#{name}Session",
25
+ # This is the related session class.
26
+ #
27
+ # * <tt>relationship_name:</tt> default: options[:session_class].klass_name.underscore.pluralize,
28
+ # This is the name of the relationship you want to use to scope everything. For example an Account has many Users. There should be a relationship
29
+ # called :users that you defined with a has_many. The reason we use the relationship is so you don't have to repeat yourself. The relatonship
30
+ # could have all kinds of custom options. So instead of repeating yourself we essentially use the scope that the relationship creates.
31
+ #
32
+ # * <tt>find_options:</tt> default: nil,
33
+ # By default the find options are created from the relationship you specify with :relationship_name. But if you want to override this and
34
+ # manually specify find_options you can do it here. Specify options just as you would in ActiveRecord::Base.find.
35
+ #
36
+ # * <tt>scope_cookies:</tt> default: false
37
+ # By the nature of cookies they scope theirself if you are using subdomains to access accounts. If you aren't using subdomains you need to have
38
+ # separate cookies for each account, assuming a user is logging into mroe than one account. Authlogic can take care of this for you by
39
+ # prefixing the name of the cookie and sessin with the model id. You just need to tell Authlogic to do this by passing this option.
40
+ def authenticates_many(name, options = {})
41
+ options[:session_class] ||= name.to_s.classify.constantize
42
+ options[:relationship_name] ||= options[:session_class].klass_name.underscore.pluralize
43
+ class_eval <<-"end_eval", __FILE__, __LINE__
44
+ def #{name}
45
+ find_options = #{options[:find_options].inspect} || #{options[:relationship_name]}.scope(:find)
46
+ find_options.delete_if { |key, value| ![:conditions, :include, :joins].include?(key.to_sym) || value.nil? }
47
+ @#{name} ||= Authlogic::AuthenticatesMany::Association.new(#{options[:session_class]}, find_options, #{options[:scope_cookies] ? "self.class.model_name.underscore + '_' + self.send(self.class.primary_key).to_s" : "nil"})
48
+ end
49
+ end_eval
50
+ end
51
+ end
52
+
53
+ ::ActiveRecord::Base.extend(Base) if defined?(::ActiveRecord)
54
+ end
55
+ end
@@ -1,8 +1,7 @@
1
1
  module Authlogic
2
2
  module ControllerAdapters # :nodoc:
3
- # = Abstract Adapter
4
- #
5
- # Allows you to use Authlogic in any framework you want, not just rails. See the RailsAdapter or MerbAdapter for an example of how to adapt Authlogic to work with your framework.
3
+ # Allows you to use Authlogic in any framework you want, not just rails. See the RailsAdapter or MerbAdapter
4
+ # for an example of how to adapt Authlogic to work with your framework.
6
5
  class AbstractAdapter
7
6
  attr_accessor :controller
8
7
 
@@ -1,12 +1,8 @@
1
1
  module Authlogic
2
2
  module ControllerAdapters
3
- # = Merb Adapter
4
- #
5
3
  # Adapts authlogic to work with merb. The point is to close the gap between what authlogic expects and what the merb controller object
6
4
  # provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
7
5
  class MerbAdapter < AbstractAdapter
8
- # = Merb Implementation
9
- #
10
6
  # Lets Authlogic know about the controller object via a before filter, AKA "activates" authlogic.
11
7
  module MerbImplementation
12
8
  def self.included(klass) # :nodoc:
@@ -1,7 +1,5 @@
1
1
  module Authlogic
2
2
  module ControllerAdapters
3
- # = Rails Adapter
4
- #
5
3
  # Adapts authlogic to work with rails. The point is to close the gap between what authlogic expects and what the rails controller object
6
4
  # provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
7
5
  class RailsAdapter < AbstractAdapter
@@ -22,8 +20,6 @@ module Authlogic
22
20
  request.format.to_s
23
21
  end
24
22
 
25
- # = Rails Implementation
26
- #
27
23
  # Lets Authlogic know about the controller object via a before filter, AKA "activates" authlogic.
28
24
  module RailsImplementation
29
25
  def self.included(klass) # :nodoc:
@@ -2,8 +2,6 @@ require "openssl"
2
2
 
3
3
  module Authlogic
4
4
  module CryptoProviders
5
- # = AES256
6
- #
7
5
  # This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
8
6
  # In an initializer, or before your application initializes, you should do the following:
9
7
  #
@@ -5,8 +5,6 @@ end
5
5
 
6
6
  module Authlogic
7
7
  module CryptoProviders
8
- # = Bcrypt
9
- #
10
8
  # For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
11
9
  # secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
12
10
  # password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
@@ -0,0 +1,34 @@
1
+ require "digest/md5"
2
+
3
+ module Authlogic
4
+ module CryptoProviders
5
+ # This class was made for the users transitioning from md5 based systems.
6
+ # I highly discourage using this crypto provider as it superbly inferior
7
+ # to your other options.
8
+ #
9
+ # Please use any other provider offered by Authlogic.
10
+ class MD5
11
+ class << self
12
+ attr_accessor :join_token
13
+
14
+ # The number of times to loop through the encryption.
15
+ def stretches
16
+ @stretches ||= 1
17
+ end
18
+ attr_writer :stretches
19
+
20
+ # Turns your raw password into a MD5 hash.
21
+ def encrypt(*tokens)
22
+ digest = tokens.flatten.join(join_token)
23
+ stretches.times { digest = Digest::MD5.hexdigest(digest) }
24
+ digest
25
+ end
26
+
27
+ # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
28
+ def matches?(crypted, *tokens)
29
+ encrypt(*tokens) == crypted
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -2,8 +2,6 @@ require "digest/sha1"
2
2
 
3
3
  module Authlogic
4
4
  module CryptoProviders
5
- # = Sha1
6
- #
7
5
  # This class was made for the users transitioning from restful_authentication. I highly discourage using this crypto provider as it inferior to your other options.
8
6
  # Please use any other provider offered by Authlogic.
9
7
  class Sha1
@@ -1,9 +1,7 @@
1
1
  require "digest/sha2"
2
2
 
3
3
  module Authlogic
4
- # = Crypto Providers
5
- #
6
- # The acts_as_authentic method allows you to pass a :crypto_provider option. This allows you to use any type of encryption you like.
4
+ # The acts_as_authentic method has a crypto_provider option. This allows you to use any type of encryption you like.
7
5
  # Just create a class with a class level encrypt and matches? method. See example below.
8
6
  #
9
7
  # === Example
@@ -1,6 +1,4 @@
1
1
  module Authlogic
2
- # I18n
3
- #
4
2
  # This class allows any message in Authlogic to use internationalization. In earlier versions of Authlogic each message was translated via configuration.
5
3
  # This cluttered up the configuration and cluttered up Authlogic. So all translation has been extracted out into this class. Now all messages pass through
6
4
  # this class, making it much easier to implement in I18n library / plugin you want. Use this as a layer that sits between Authlogic and whatever I18n
@@ -40,8 +38,7 @@ module Authlogic
40
38
  # not_active: Your account is not active
41
39
  # not_confirmed: Your account is not confirmed
42
40
  # not_approved: Your account is not approved
43
- # blank_record: You can not login with a blank record
44
- # new_record: You can not login with a new record
41
+ # no_authentication_details: You did not provide any details for authentication.
45
42
  class I18n
46
43
  class << self
47
44
  # All message translation is passed to this method. The first argument is the key for the message. The second is options, see the rails I18n library for a list of options used.
@@ -0,0 +1,33 @@
1
+ module Authlogic
2
+ # Handles generating random strings. If SecureRandom is installed it will default to this and use it instead. SecureRandom comes with ActiveSupport.
3
+ # So if you are using this in a rails app you should have this library.
4
+ module Random
5
+ extend self
6
+
7
+ SecureRandom = (defined?(::SecureRandom) && ::SecureRandom) || (defined?(::ActiveSupport::SecureRandom) && ::ActiveSupport::SecureRandom)
8
+
9
+ if SecureRandom
10
+ def hex_token
11
+ SecureRandom.hex(64)
12
+ end
13
+
14
+ def friendly_token
15
+ # use base64url as defined by RFC4648
16
+ SecureRandom.base64(15).tr('+/=', '-_ ').strip.delete("\n")
17
+ end
18
+ else
19
+ def hex_token
20
+ Authlogic::CryptoProviders::Sha512.encrypt(Time.now.to_s + (1..10).collect{ rand.to_s }.join)
21
+ end
22
+
23
+ FRIENDLY_CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
24
+
25
+ def friendly_token
26
+ newpass = ""
27
+ 1.upto(20) { |i| newpass << FRIENDLY_CHARS[rand(FRIENDLY_CHARS.size-1)] }
28
+ newpass
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ module Authlogic
2
+ module Session
3
+ # Activating Authlogic requires that you pass it an Authlogic::ControllerAdapters::AbstractAdapter object, or a class that extends it.
4
+ # This is sort of like a database connection for an ORM library, Authlogic can't do anything until it is "connected" to a controller.
5
+ # If you are using a supported framework, Authlogic takes care of this for you.
6
+ module Activation
7
+ class NotActivatedError < ::StandardError # :nodoc:
8
+ def initialize(session)
9
+ super("You must activate the Authlogic::Session::Base.controller with a controller object before creating objects")
10
+ end
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.class_eval do
15
+ extend ClassMethods
16
+ include InstanceMethods
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ # Returns true if a controller has been set and can be used properly. This MUST be set before anything can be done.
22
+ # Similar to how ActiveRecord won't allow you to do anything without establishing a DB connection. In your framework
23
+ # environment this is done for you, but if you are using Authlogic outside of your framework, you need to assign a controller
24
+ # object to Authlogic via Authlogic::Session::Base.controller = obj. See the controller= method for more information.
25
+ def activated?
26
+ !controller.nil?
27
+ end
28
+
29
+ # This accepts a controller object wrapped with the Authlogic controller adapter. The controller adapters close the gap
30
+ # between the different controllers in each framework. That being said, Authlogic is expecting your object's class to
31
+ # extend Authlogic::ControllerAdapters::AbstractAdapter. See Authlogic::ControllerAdapters for more info.
32
+ def controller=(value)
33
+ Thread.current[:authlogic_controller] = value
34
+ end
35
+
36
+ # The current controller object
37
+ def controller
38
+ Thread.current[:authlogic_controller]
39
+ end
40
+ end
41
+
42
+ module InstanceMethods
43
+ # Making sure we are activated before we start creating objects
44
+ def initialize(*args)
45
+ raise NotActivatedError.new(self) unless self.class.activated?
46
+ super
47
+ end
48
+
49
+ private
50
+ def controller
51
+ self.class.controller
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,22 +1,30 @@
1
1
  module Authlogic
2
2
  module Session
3
- # = ActiveRecord Trickery
4
- #
5
- # Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not ActiveRecord. That's the goal here. This is useful for the various rails helper methods such as form_for, error_messages_for, or any
6
- # method that expects an ActiveRecord object. The point is to disguise the object as an ActiveRecord object so we have no problems.
3
+ # Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not ActiveRecord. That's the goal here.
4
+ # This is useful for the various rails helper methods such as form_for, error_messages_for, or any method that
5
+ # expects an ActiveRecord object. The point is to disguise the object as an ActiveRecord object so we can take
6
+ # advantage of the many ActiveRecord tools.
7
7
  module ActiveRecordTrickery
8
- def self.included(klass) # :nodoc:
8
+ def self.included(klass)
9
9
  klass.extend ClassMethods
10
10
  klass.send(:include, InstanceMethods)
11
11
  end
12
12
 
13
- module ClassMethods # :nodoc:
13
+ module ClassMethods
14
14
  def human_attribute_name(*args)
15
15
  klass.human_attribute_name(*args)
16
16
  end
17
+
18
+ def human_name(*args)
19
+ klass.human_name(*args)
20
+ end
21
+
22
+ def self_and_descendents_from_active_record
23
+ [self]
24
+ end
17
25
  end
18
26
 
19
- module InstanceMethods # :nodoc:
27
+ module InstanceMethods
20
28
  def new_record?
21
29
  new_session?
22
30
  end
@@ -1,462 +1,37 @@
1
1
  module Authlogic
2
2
  module Session # :nodoc:
3
- # = Base
4
- #
5
- # This is the muscle behind Authlogic. For detailed information on how to use this please refer to the README. For detailed method explanations see below.
3
+ # This is the base class Authlogic, where all modules are included. For information on functiionality see the various
4
+ # sub modules.
6
5
  class Base
7
- include Config
8
-
9
- class << self
10
- attr_accessor :methods_configured
11
-
12
- # Returns true if a controller has been set and can be used properly. This MUST be set before anything can be done. Similar to how ActiveRecord won't allow you to do anything
13
- # without establishing a DB connection. In your framework environment this is done for you, but if you are using Authlogic outside of your framework, you need to assign a controller
14
- # object to Authlogic via Authlogic::Session::Base.controller = obj. See the controller= method for more information.
15
- def activated?
16
- !controller.nil?
17
- end
18
-
19
- # This accepts a controller object wrapped with the Authlogic controller adapter. The controller adapters close the gap between the different controllers in each framework.
20
- # That being said, Authlogic is expecting your object's class to extend Authlogic::ControllerAdapters::AbstractAdapter. See Authlogic::ControllerAdapters for more info.
21
- def controller=(value)
22
- Thread.current[:authlogic_controller] = value
23
- end
24
-
25
- def controller # :nodoc:
26
- Thread.current[:authlogic_controller]
27
- end
28
-
29
- # A convenince method. The same as:
30
- #
31
- # session = UserSession.new
32
- # session.create
33
- def create(*args, &block)
34
- session = new(*args)
35
- session.save(&block)
36
- end
37
-
38
- # Same as create but calls create!, which raises an exception when authentication fails.
39
- def create!(*args)
40
- session = new(*args)
41
- session.save!
42
- end
43
-
44
- # A convenience method for session.find_record. Finds your session by parameters, then session, then cookie, and finally by basic http auth.
45
- # This is perfect for persisting your session:
46
- #
47
- # helper_method :current_user_session, :current_user
48
- #
49
- # def current_user_session
50
- # return @current_user_session if defined?(@current_user_session)
51
- # @current_user_session = UserSession.find
52
- # end
53
- #
54
- # def current_user
55
- # return @current_user if defined?(@current_user)
56
- # @current_user = current_user_session && current_user_session.user
57
- # end
58
- #
59
- # Accepts a single parameter as the id, to find session that you marked with an id:
60
- #
61
- # UserSession.find(:secure)
62
- #
63
- # See the id method for more information on ids.
64
- def find(id = nil, priority_record = nil)
65
- session = new(id)
66
- session.before_find
67
- if record = session.find_record
68
- session.after_find
69
- record.save_without_session_maintenance(false) if record.changed? && record != priority_record
70
- session
71
- else
72
- nil
73
- end
74
- end
75
-
76
- # The name of the class that this session is authenticating with. For example, the UserSession class will authenticate with the User class
77
- # unless you specify otherwise in your configuration. See authenticate_with for information on how to change this value.
78
- def klass
79
- @klass ||=
80
- if klass_name
81
- klass_name.constantize
82
- else
83
- nil
84
- end
85
- end
86
-
87
- # Same as klass, just returns a string instead of the actual constant.
88
- def klass_name
89
- @klass_name ||=
90
- if guessed_name = name.scan(/(.*)Session/)[0]
91
- @klass_name = guessed_name[0]
92
- end
93
- end
94
- end
95
-
96
- attr_accessor :attempted_record, :new_session, :record
97
- attr_reader :unauthorized_record
98
- attr_writer :authenticating_with, :id, :persisting
99
-
100
- # You can initialize a session by doing any of the following:
101
- #
102
- # UserSession.new
103
- # UserSession.new(:login => "login", :password => "password", :remember_me => true)
104
- # UserSession.new(User.first, true)
105
- #
106
- # If a user has more than one session you need to pass an id so that Authlogic knows how to differentiate the sessions. The id MUST be a Symbol.
107
- #
108
- # UserSession.new(:my_id)
109
- # UserSession.new({:login => "login", :password => "password", :remember_me => true}, :my_id)
110
- # UserSession.new(User.first, true, :my_id)
111
- #
112
- # For more information on ids see the id method.
113
- #
114
- # Lastly, the reason the id is separate from the first parameter hash is becuase this should be controlled by you, not by what the user passes.
115
- # A user could inject their own id and things would not work as expected.
116
- def initialize(*args)
117
- raise NotActivated.new(self) unless self.class.activated?
118
-
119
- create_configurable_methods!
120
-
121
- self.id = args.pop if args.last.is_a?(Symbol)
122
-
123
- if args.first.is_a?(Hash)
124
- self.credentials = args.first
125
- elsif !args.first.blank? && args.first.class < ::ActiveRecord::Base
126
- self.unauthorized_record = args.first
127
- self.remember_me = args[1] if args.size > 1
128
- end
129
- end
130
-
131
- # A flag for how the user is logging in. Possible values:
132
- #
133
- # * <tt>:password</tt> - username and password
134
- # * <tt>:unauthorized_record</tt> - an actual ActiveRecord object
135
- #
136
- # By default this is :password
137
- def authenticating_with
138
- @authenticating_with ||= :password
139
- end
140
-
141
- # Returns true if logging in with credentials. Credentials mean username and password.
142
- def authenticating_with_password?
143
- authenticating_with == :password
144
- end
145
-
146
- # Returns true if logging in with an unauthorized record
147
- def authenticating_with_unauthorized_record?
148
- authenticating_with == :unauthorized_record
149
- end
150
- alias_method :authenticating_with_record?, :authenticating_with_unauthorized_record?
151
-
152
- # Your login credentials in hash format. Usually {:login => "my login", :password => "<protected>"} depending on your configuration.
153
- # Password is protected as a security measure. The raw password should never be publicly accessible.
154
- def credentials
155
- {login_field => send(login_field), password_field => "<Protected>"}
156
- end
157
-
158
- # Lets you set your loging and password via a hash format. This is "params" safe. It only allows for 3 keys: your login field name, password field name, and remember me.
159
- def credentials=(values)
160
- return if values.blank? || !values.is_a?(Hash)
161
- values.symbolize_keys!
162
- values.each do |field, value|
163
- next if value.blank?
164
- send("#{field}=", value)
165
- end
166
- end
167
-
168
- # Resets everything, your errors, record, cookies, and session. Basically "logs out" a user.
169
- def destroy
170
- before_destroy
171
-
172
- errors.clear
173
- @record = nil
174
-
175
- after_destroy
176
-
177
- true
178
- end
179
-
180
- # The errors in Authlogic work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:
181
- #
182
- # === Example
183
- #
184
- # class UserSession
185
- # before_validation :check_if_awesome
186
- #
187
- # private
188
- # def check_if_awesome
189
- # errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
190
- # errors.add_to_base("You must be awesome to log in") unless record.awesome?
191
- # end
192
- # end
193
- def errors
194
- @errors ||= Errors.new(self)
195
- end
196
-
197
- # Attempts to find the record by params, then session, then cookie, and finally basic http auth. See the class level find method if you are wanting to use this to persist your session.
198
- def find_record
199
- if record
200
- self.new_session = false
201
- return record
202
- end
203
-
204
- find_with.each do |find_method|
205
- if send("valid_#{find_method}?")
206
- self.new_session = false
207
- return record
208
- end
209
- end
210
- nil
211
- end
212
-
213
- # Allows you to set a unique identifier for your session, so that you can have more than 1 session at a time. A good example when this might be needed is when you want to have a normal user session
214
- # and a "secure" user session. The secure user session would be created only when they want to modify their billing information, or other sensitive information. Similar to me.com. This requires 2
215
- # user sessions. Just use an id for the "secure" session and you should be good.
216
- #
217
- # You can set the id during initialization (see initialize for more information), or as an attribute:
218
- #
219
- # session.id = :my_id
220
- #
221
- # Just be sure and set your id before you save your session.
222
- #
223
- # Lastly, to retrieve your session with the id check out the find class method.
224
- def id
225
- @id
226
- end
227
-
228
- def inspect # :nodoc:
229
- details = {}
230
- case authenticating_with
231
- when :unauthorized_record
232
- details[:unauthorized_record] = "<protected>"
233
- else
234
- details[login_field.to_sym] = send(login_field)
235
- details[password_field.to_sym] = "<protected>"
236
- end
237
- "#<#{self.class.name} #{details.inspect}>"
238
- end
239
-
240
- # Similar to ActiveRecord's new_record? Returns true if the session has not been saved yet.
241
- def new_session?
242
- new_session != false
243
- end
244
-
245
- def persisting # :nodoc:
246
- return @persisting if defined?(@persisting)
247
- @persisting = true
248
- end
249
-
250
- # Returns true if the session is being persisted. This is set to false if the session was found by the single_access_token, since logging in via a single access token should not remember the user in the
251
- # session or the cookie.
252
- def persisting?
253
- persisting == true
254
- end
255
-
256
- def remember_me # :nodoc:
257
- return @remember_me if defined?(@remember_me)
258
- @remember_me = self.class.remember_me
259
- end
260
-
261
- # Accepts a boolean as a flag to remember the session or not. Basically to expire the cookie at the end of the session or keep it for "remember_me_until".
262
- def remember_me=(value)
263
- @remember_me = value
264
- end
265
-
266
- # Allows users to be remembered via a cookie.
267
- def remember_me?
268
- remember_me == true || remember_me == "true" || remember_me == "1"
269
- end
270
-
271
- # When to expire the cookie. See remember_me_for configuration option to change this.
272
- def remember_me_until
273
- return unless remember_me?
274
- remember_me_for.from_now
275
- end
276
-
277
- # Creates / updates a new user session for you. It does all of the magic:
278
- #
279
- # 1. validates
280
- # 2. sets session
281
- # 3. sets cookie
282
- # 4. updates magic fields
283
- def save(&block)
284
- result = nil
285
- if valid?
286
- # hooks
287
- before_save
288
- new_session? ? before_create : before_update
289
- new_session? ? after_create : after_update
290
- after_save
291
-
292
- record.save_without_session_maintenance(false) if record.changed?
293
- self.new_session = false
294
- result = self
295
- else
296
- result = false
297
- end
298
-
299
- yield result if block_given?
300
- result
301
- end
302
-
303
- # Same as save but raises an exception when authentication fails
304
- def save!
305
- result = save
306
- raise SessionInvalid.new(self) unless result
307
- result
308
- end
309
-
310
- # This lets you create a session by passing a single object of whatever you are authenticating. Let's say User. By passing a user object you are vouching for this user and saying you can guarantee
311
- # this user is who he says he is, create a session for him.
312
- #
313
- # This is how persistence works in Authlogic. Authlogic grabs your cookie credentials, finds a user by those credentials, and then vouches for that user and creates a session. You can do this for just about
314
- # anything, which comes in handy for those unique authentication methods. Do what you need to do to authenticate the user, guarantee he is who he says he is, then pass the object here. Authlogic will do its
315
- # magic: create a session and cookie. Now when the user refreshes their session will be persisted by their session and cookie.
316
- def unauthorized_record=(value)
317
- self.authenticating_with = :unauthorized_record
318
- @unauthorized_record = value
319
- end
320
-
321
- # Returns if the session is valid or not. Basically it means that a record could or could not be found. If the session is valid you will have a result when calling the "record" method. If it was unsuccessful
322
- # you will not have a record.
323
- def valid?
324
- errors.clear
325
- self.attempted_record = nil
326
-
327
- before_validation
328
- new_session? ? before_validation_on_create : before_validation_on_update
329
- valid_credentials?
330
- validate
331
-
332
- if errors.empty?
333
- new_session? ? after_validation_on_create : after_validation_on_update
334
- after_validation
335
- else
336
- self.record = nil
337
- end
338
-
339
- attempted_record.save_without_session_maintenance(false) if attempted_record && attempted_record.changed?
340
- self.attempted_record = nil
341
- errors.empty?
342
- end
343
-
344
- # Tries to validate the session from information from a basic http auth, if it was provided.
345
- def valid_http_auth?
346
- controller.authenticate_with_http_basic do |login, password|
347
- if !login.blank? && !password.blank?
348
- send("#{login_field}=", login)
349
- send("#{password_field}=", password)
350
- return valid?
351
- end
352
- end
353
-
354
- false
355
- end
356
-
357
- private
358
- def controller
359
- self.class.controller
360
- end
361
-
362
- # The goal with Authlogic is to feel as natural as possible. As a result, this method creates methods on the fly
363
- # based on the configuration set. By default the configuration is based off of the columns names in the authenticating
364
- # model. Thus allowing you to call user_session.username instead of user_session.login if you have a username column
365
- # instead of a login column. Since class configuration can change during initialization it makes the most sense to enforce
366
- # this configuration during the first initialization. At this point, all configuration should be set.
367
- #
368
- # Lastly, each method is defined individually to allow the user to provide their own "custom" method and this makes sure
369
- # we don't replace their method.
370
- def create_configurable_methods!
371
- return if self.class.methods_configured == true
372
-
373
- self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
374
- self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
375
- self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
376
- self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
377
- self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
378
-
379
- self.class.class_eval <<-"end_eval", __FILE__, __LINE__
380
- def #{login_field}_with_authentication_flag=(value)
381
- self.authenticating_with = :password
382
- self.#{login_field}_without_authentication_flag = value
383
- end
384
- alias_method_chain :#{login_field}=, :authentication_flag
385
-
386
- def #{password_field}_with_authentication_flag=(value)
387
- self.authenticating_with = :password
388
- self.#{password_field}_without_authentication_flag = value
389
- end
390
- alias_method_chain :#{password_field}=, :authentication_flag
391
-
392
- private
393
- # The password should not be accessible publicly. This way forms using form_for don't fill the password with the attempted password. The prevent this we just create this method that is private.
394
- def protected_#{password_field}
395
- @#{password_field}
396
- end
397
- end_eval
398
-
399
- self.class.methods_configured = true
400
- end
401
-
402
- def klass
403
- self.class.klass
404
- end
405
-
406
- def klass_name
407
- self.class.klass_name
408
- end
409
-
410
- def search_for_record(method, value)
411
- klass.send(method, value)
412
- end
413
-
414
- def valid_credentials?
415
- case authenticating_with
416
- when :password
417
- errors.add(login_field, I18n.t('error_messages.login_blank', :default => "can not be blank")) if send(login_field).blank?
418
- errors.add(password_field, I18n.t('error_messages.password_blank', :default => "can not be blank")) if send("protected_#{password_field}").blank?
419
- return false if errors.count > 0
420
-
421
- self.attempted_record = search_for_record(find_by_login_method, send(login_field))
422
-
423
- if attempted_record.blank?
424
- errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "does not exist"))
425
- return false
426
- end
427
-
428
- unless attempted_record.send(verify_password_method, send("protected_#{password_field}"))
429
- errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
430
- return false
431
- end
432
- when :unauthorized_record
433
- self.attempted_record = unauthorized_record
434
-
435
- if attempted_record.blank?
436
- errors.add_to_base(I18n.t('error_messages.blank_record', :default => "You can not login with a blank record"))
437
- return false
438
- end
439
-
440
- if attempted_record.new_record?
441
- errors.add_to_base(I18n.t('error_messages.new_record', :default => "You can not login with a new record"))
442
- return false
443
- end
444
- end
445
-
446
- self.record = attempted_record
447
- valid_record?
448
- end
449
-
450
- def valid_record?
451
- return true if disable_magic_states?
452
- [:active, :approved, :confirmed].each do |required_status|
453
- if record.respond_to?("#{required_status}?") && !record.send("#{required_status}?")
454
- errors.add_to_base(I18n.t("error_messages.not_#{required_status}", :default => "Your account is not #{required_status}"))
455
- return false
456
- end
457
- end
458
- true
459
- end
6
+ include Foundation
7
+ include Callbacks
8
+
9
+ # Included first so that the session resets itself to nil
10
+ include Timeout
11
+
12
+ # Included in a specific order so they are tried in this order when persisting
13
+ include Params
14
+ include Cookies
15
+ include Session
16
+ include HttpAuth
17
+
18
+ # Included in a specific order so magic states gets ran after a record is found
19
+ include Password
20
+ include UnauthorizedRecord
21
+ include MagicStates
22
+
23
+ include Activation
24
+ include ActiveRecordTrickery
25
+ include BruteForceProtection
26
+ include Existence
27
+ include Klass
28
+ include MagicColumns
29
+ include PerishableToken
30
+ include Persistence
31
+ include Scopes
32
+ include Id
33
+ include Validation
34
+ include PriorityRecord
460
35
  end
461
36
  end
462
37
  end