Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support UTF-8 data when using the JSON serializer #39

Merged
merged 4 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/rack/session/encryptor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ module Serializable
def serialize_payload(message)
serialized_data = serializer.dump(message)

return "#{[0].pack('v')}#{serialized_data}" if @options[:pad_size].nil?
return "#{[0].pack('v')}#{serialized_data.force_encoding(Encoding::BINARY)}" if @options[:pad_size].nil?

padding_bytes = @options[:pad_size] - (2 + serialized_data.size) % @options[:pad_size]
padding_data = SecureRandom.random_bytes(padding_bytes)

"#{[padding_bytes].pack('v')}#{padding_data}#{serialized_data}"
"#{[padding_bytes].pack('v')}#{padding_data}#{serialized_data.force_encoding(Encoding::BINARY)}"
end

# Return the deserialized message. The first 2 bytes will be read as the
Expand Down Expand Up @@ -103,7 +103,7 @@ def initialize(secret, opts = {})
serialize_json: false, pad_size: 32, purpose: nil
}.update(opts)

@hmac_secret = secret.dup.force_encoding('BINARY')
@hmac_secret = secret.dup.force_encoding(Encoding::BINARY)
@cipher_secret = @hmac_secret.slice!(0, 32)

@hmac_secret.freeze
Expand Down Expand Up @@ -250,7 +250,7 @@ def initialize(secret, opts = {})
}.update(opts)
@options[:serialize_json] = true # Enforce JSON serialization

@cipher_secret = secret.dup.force_encoding('BINARY').slice!(0, 32)
@cipher_secret = secret.dup.force_encoding(Encoding::BINARY).slice!(0, 32)
@cipher_secret.freeze
end

Expand Down
14 changes: 7 additions & 7 deletions test/spec_session_cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def decode(str); @calls << :decode; JSON.parse(str); end
response.body.must_equal '{"counter"=>1}'
identity.calls.must_equal [:encode]

response = response_for(app: [incrementor, { coder: identity }], :cookie=>response["Set-Cookie"].split(';', 2).first)
response_for(app: [incrementor, { coder: identity }], cookie: response["Set-Cookie"].split(';', 2).first)
identity.calls.must_equal [:encode, :decode, :encode]
end

Expand All @@ -223,20 +223,20 @@ def decode(str); @calls << :decode; JSON.parse(str); end
end

it "passes through same_site option to session cookie" do
response = response_for(app: [incrementor, same_site: :none])
response["Set-Cookie"].must_include "SameSite=None"
response = response_for(app: [incrementor, { same_site: :none }])
assert(response["Set-Cookie"].include?("SameSite=None") || response["Set-Cookie"].include?("samesite=none"))
end

it "allows using a lambda to specify same_site option, because some browsers require different settings" do
# Details of why this might need to be set dynamically:
# https://www.chromium.org/updates/same-site/incompatible-clients
# https://gist.github.com/bnorton/7dee72023787f367c48b3f5c2d71540f

response = response_for(app: [incrementor, same_site: lambda { |req, res| :none }])
response["Set-Cookie"].must_include "SameSite=None"
response = response_for(app: [incrementor, { same_site: lambda { |req, res| :none } }])
assert(response["Set-Cookie"].include?("SameSite=None") || response["Set-Cookie"].include?("samesite=none"))

response = response_for(app: [incrementor, same_site: lambda { |req, res| :lax }])
response["Set-Cookie"].must_include "SameSite=Lax"
response = response_for(app: [incrementor, { same_site: lambda { |req, res| :lax } }])
assert(response["Set-Cookie"].include?("SameSite=Lax") || response["Set-Cookie"].include?("samesite=lax"))
end

it "loads from a cookie" do
Expand Down
12 changes: 12 additions & 0 deletions test/spec_session_encryptor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ def self.included(_base)
encryptor.decrypt(message).must_equal({ 'foo' => 'bar' })
end

# The V1 encryptor defaults to the Marshal serializer, while the V2
# encryptor always uses the JSON serializer. This means that we are
# indirectly covering both serializers.
it 'decrypts an encrypted message with UTF-8 data' do
encryptor = encryptor_class.new(@secret)

encrypted_message = encryptor.encrypt({ 'foo' => 'bar 😀' })
decrypted_message = encryptor.decrypt(encrypted_message)

decrypted_message.must_equal({ 'foo' => 'bar 😀' })
end

it 'decrypts raises InvalidSignature without purpose' do
encryptor = encryptor_class.new(@secret, purpose: 'testing')
other_encryptor = encryptor_class.new(@secret)
Expand Down
6 changes: 3 additions & 3 deletions test/spec_session_pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,19 @@
pool.same_site.must_equal :none
req = Rack::MockRequest.new(pool)
res = req.get("/")
res["Set-Cookie"].must_include "SameSite=None"
assert(res["Set-Cookie"].include?("SameSite=None") || res["Set-Cookie"].include?("samesite=none"))
end

it "allows using a lambda to specify same_site option, because some browsers require different settings" do
pool = Rack::Session::Pool.new(incrementor, same_site: lambda { |req, res| :none })
req = Rack::MockRequest.new(pool)
res = req.get("/")
res["Set-Cookie"].must_include "SameSite=None"
assert(res["Set-Cookie"].include?("SameSite=None") || res["Set-Cookie"].include?("samesite=none"))

pool = Rack::Session::Pool.new(incrementor, same_site: lambda { |req, res| :lax })
req = Rack::MockRequest.new(pool)
res = req.get("/")
res["Set-Cookie"].must_include "SameSite=Lax"
assert(res["Set-Cookie"].include?("SameSite=Lax") || res["Set-Cookie"].include?("samesite=lax"))
end

# anyone know how to do this better?
Expand Down