mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #36434 from Edouard-chin/ec-securecompare-rotation
Introduce a new ActiveSupport::SecureCompareRotator class:
This commit is contained in:
commit
bcc3d625b4
2 changed files with 96 additions and 0 deletions
52
activesupport/lib/active_support/secure_compare_rotator.rb
Normal file
52
activesupport/lib/active_support/secure_compare_rotator.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "active_support/security_utils"
|
||||
require "active_support/messages/rotator"
|
||||
|
||||
module ActiveSupport
|
||||
# The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+
|
||||
# and allows you to rotate a previously defined value to a new one.
|
||||
#
|
||||
# It can be used as follow:
|
||||
#
|
||||
# rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
|
||||
# rotator.rotate('previous_production_value')
|
||||
# rotator.secure_compare!('previous_production_value')
|
||||
#
|
||||
# One real use case example would be to rotate a basic auth credentials:
|
||||
#
|
||||
# class MyController < ApplicationController
|
||||
# def authenticate_request
|
||||
# rotator = ActiveSupport::SecureComparerotator.new('new_password')
|
||||
# rotator.rotate('old_password')
|
||||
#
|
||||
# authenticate_or_request_with_http_basic do |username, password|
|
||||
# rotator.secure_compare!(password)
|
||||
# rescue ActiveSupport::SecureCompareRotator::InvalidMatch
|
||||
# false
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
class SecureCompareRotator
|
||||
include SecurityUtils
|
||||
prepend Messages::Rotator
|
||||
|
||||
InvalidMatch = Class.new(StandardError)
|
||||
|
||||
def initialize(value, **_options)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def secure_compare!(other_value, on_rotation: @rotation)
|
||||
secure_compare(@value, other_value) ||
|
||||
run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } ||
|
||||
raise(InvalidMatch)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_rotation(previous_value, _options)
|
||||
self.class.new(previous_value)
|
||||
end
|
||||
end
|
||||
end
|
44
activesupport/test/secure_compare_rotator_test.rb
Normal file
44
activesupport/test/secure_compare_rotator_test.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "abstract_unit"
|
||||
require "active_support/secure_compare_rotator"
|
||||
|
||||
class SecureCompareRotatorTest < ActiveSupport::TestCase
|
||||
test "#secure_compare! works correctly after rotation" do
|
||||
wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
|
||||
wrapper.rotate("new_secret")
|
||||
|
||||
assert_equal(true, wrapper.secure_compare!("new_secret"))
|
||||
end
|
||||
|
||||
test "#secure_compare! works correctly after multiple rotation" do
|
||||
wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
|
||||
wrapper.rotate("new_secret")
|
||||
wrapper.rotate("another_secret")
|
||||
wrapper.rotate("and_another_one")
|
||||
|
||||
assert_equal(true, wrapper.secure_compare!("and_another_one"))
|
||||
end
|
||||
|
||||
test "#secure_compare! fails correctly when credential is not part of the rotation" do
|
||||
wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
|
||||
wrapper.rotate("new_secret")
|
||||
|
||||
assert_raises(ActiveSupport::SecureCompareRotator::InvalidMatch) do
|
||||
wrapper.secure_compare!("different_secret")
|
||||
end
|
||||
end
|
||||
|
||||
test "#secure_compare! calls the on_rotation proc" do
|
||||
wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
|
||||
wrapper.rotate("new_secret")
|
||||
wrapper.rotate("another_secret")
|
||||
wrapper.rotate("and_another_one")
|
||||
|
||||
@witness = nil
|
||||
|
||||
assert_changes(:@witness, from: nil, to: true) do
|
||||
assert_equal(true, wrapper.secure_compare!("and_another_one", on_rotation: -> { @witness = true }))
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue