Skip to content

Commit

Permalink
Forward-porting encrypted_cookies from Authlogic 4.4
Browse files Browse the repository at this point in the history
  • Loading branch information
tiegz committed May 2, 2020
1 parent 7da4f14 commit cb1ff2b
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 13 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* Breaking Changes
* None
* Added
* None
* [#666](https://github.com/binarylogic/authlogic/pull/666) -
Forwardported Authlogic::Session::Cookies.encrypt_cookie option
* Fixed
* None

Expand Down
56 changes: 44 additions & 12 deletions lib/authlogic/session/base.rb
Expand Up @@ -961,6 +961,20 @@ def sign_cookie(value = nil)
end
alias sign_cookie= sign_cookie

# Should the cookie be encrypted? If the controller adapter supports it, this is a
# measure to hide the contents of the cookie (e.g. persistence_token)
def encrypt_cookie(value = nil)
if value && !controller.cookies.respond_to?(:encrypted)
raise "Encrypted cookies not supported with #{controller.class}!"
end
if value && sign_cookie
raise "It is recommended to use encrypt_cookie instead of sign_cookie. " \
"You may not enable both options."
end
rw_config(:encrypt_cookie, value, false)
end
alias_method :encrypt_cookie=, :encrypt_cookie

# Works exactly like cookie_key, but for sessions. See cookie_key for more info.
#
# * <tt>Default:</tt> cookie_key
Expand Down Expand Up @@ -1480,6 +1494,23 @@ def sign_cookie?
sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
end

# If the cookie should be encrypted
def encrypt_cookie
return @encrypt_cookie if defined?(@encrypt_cookie)
@encrypt_cookie = self.class.encrypt_cookie
end

# Accepts a boolean as to whether the cookie should be encrypted. If true
# the cookie will be saved in an encrypted state.
def encrypt_cookie=(value)
@encrypt_cookie = value
end

# See encrypt_cookie
def encrypt_cookie?
encrypt_cookie == true || encrypt_cookie == "true" || encrypt_cookie == "1"
end

# The scope of the current object
def scope
@scope ||= {}
Expand Down Expand Up @@ -1623,7 +1654,9 @@ def cookie_credentials
end

def cookie_jar
if self.class.sign_cookie
if self.class.encrypt_cookie
controller.cookies.encrypted
elsif self.class.sign_cookie
controller.cookies.signed
else
controller.cookies
Expand Down Expand Up @@ -1705,13 +1738,8 @@ def generalize_credentials_error_messages?

# @api private
def generate_cookie_for_saving
creds = ::Authlogic::CookieCredentials.new(
record.persistence_token,
record.send(record.class.primary_key),
remember_me? ? remember_me_until : nil
)
{
value: creds.to_s,
value: generate_cookie_value.to_s,
expires: remember_me_until,
secure: secure,
httponly: httponly,
Expand All @@ -1720,6 +1748,14 @@ def generate_cookie_for_saving
}
end

def generate_cookie_value
::Authlogic::CookieCredentials.new(
record.persistence_token,
record.send(record.class.primary_key),
remember_me? ? remember_me_until : nil
)
end

# Returns a Proc to be executed by
# `ActionController::HttpAuthentication::Basic` when credentials are
# present in the HTTP request.
Expand Down Expand Up @@ -1935,11 +1971,7 @@ def single_access_allowed_request_types
end

def save_cookie
if sign_cookie?
controller.cookies.signed[cookie_key] = generate_cookie_for_saving
else
controller.cookies[cookie_key] = generate_cookie_for_saving
end
cookie_jar[cookie_key] = generate_cookie_for_saving
end

# @api private
Expand Down
35 changes: 35 additions & 0 deletions lib/authlogic/test_case/mock_cookie_jar.rb
Expand Up @@ -23,6 +23,10 @@ def delete(key, _options = {})
def signed
@signed ||= MockSignedCookieJar.new(self)
end

def encrypted
@encrypted ||= MockEncryptedCookieJar.new(self)
end
end

# A mock of `ActionDispatch::Cookies::SignedKeyRotatingCookieJar`
Expand All @@ -35,6 +39,7 @@ class MockSignedCookieJar < MockCookieJar

def initialize(parent_jar)
@parent_jar = parent_jar
parent_jar.each { |k, v| self[k] = v }
end

def [](val)
Expand All @@ -51,5 +56,35 @@ def []=(key, options)
@parent_jar[key] = options
end
end

class MockEncryptedCookieJar < MockCookieJar
attr_reader :parent_jar # helper for testing

def initialize(parent_jar)
@parent_jar = parent_jar
parent_jar.each { |k, v| self[k] = v }
end

def [](val)
encrypted_message = @parent_jar[val]
if encrypted_message
self.class.decrypt(encrypted_message)
end
end

def []=(key, options)
options[:value] = self.class.encrypt(options[:value])
@parent_jar[key] = options
end

# simple caesar cipher for testing
def self.encrypt(str)
str.unpack("U*").map(&:succ).pack("U*")
end

def self.decrypt(str)
str.unpack("U*").map(&:pred).pack("U*")
end
end
end
end
16 changes: 16 additions & 0 deletions test/session_test/cookies_test.rb
Expand Up @@ -182,6 +182,22 @@ def test_after_save_save_cookie
)
end

def test_after_save_save_cookie_encrypted
ben = users(:ben)

assert_nil controller.cookies["user_credentials"]
payload = "#{ben.persistence_token}::#{ben.id}"

session = UserSession.new(ben)
session.encrypt_cookie = true
assert session.save
assert_equal payload, controller.cookies.encrypted["user_credentials"]
assert_equal(
Authlogic::TestCase::MockEncryptedCookieJar.encrypt(payload),
controller.cookies.encrypted.parent_jar["user_credentials"]
)
end

def test_after_save_save_cookie_signed
ben = users(:ben)

Expand Down

0 comments on commit cb1ff2b

Please sign in to comment.