aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib
diff options
context:
space:
mode:
authorEdouard CHIN <edouard.chin@shopify.com>2019-06-06 17:50:31 +0200
committerEdouard CHIN <edouard.chin@shopify.com>2019-06-06 19:08:53 +0200
commit123bcf5faa6d6963862a33489b2d678d6ef3c137 (patch)
tree80b3da27221b032aacf7b2c13729e46b7d3be1a0 /activesupport/lib
parent480d9f2d2431e6a11c89edb45123181f9b6db958 (diff)
downloadrails-123bcf5faa6d6963862a33489b2d678d6ef3c137.tar.gz
rails-123bcf5faa6d6963862a33489b2d678d6ef3c137.tar.bz2
rails-123bcf5faa6d6963862a33489b2d678d6ef3c137.zip
Introduce a new ActiveSupport::SecureCompareRotator class:
- This class is used to rotate a previously determined value to a new one before making the comparions. We use this at Shopify to rotate Basic Auth crendials but I can imagine other use cases. The implementation uses the same `Messages::Rotator` module than the MessageEncryptor/MessageVerifier class so it works exactly the same way. You can use it as follow: ```ruby rotator = ActiveSupport::SecureCompareRotator.new('new_production_value') rotator.rotate('previous_production_value') rotator.secure_compare!('previous_production_value') ```
Diffstat (limited to 'activesupport/lib')
-rw-r--r--activesupport/lib/active_support/secure_compare_rotator.rb52
1 files changed, 52 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/secure_compare_rotator.rb b/activesupport/lib/active_support/secure_compare_rotator.rb
new file mode 100644
index 0000000000..14a0aee947
--- /dev/null
+++ b/activesupport/lib/active_support/secure_compare_rotator.rb
@@ -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