-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #638 from alphagov/apply-constraints-bulk-signatur…
…e-ops Harden bulk signature actions against attack
- Loading branch information
Showing
6 changed files
with
154 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
module BulkVerification | ||
extend ActiveSupport::Concern | ||
|
||
class InvalidBulkRequest < RuntimeError; end | ||
|
||
included do | ||
before_action :verify_bulk_request, if: :bulk_request? | ||
|
||
helper_method :bulk_verifier | ||
|
||
rescue_from ActiveSupport::MessageVerifier::InvalidSignature do | ||
raise BulkVerification::InvalidBulkRequest, "Invalid bulk request for #{selected_ids.inspect}" | ||
end | ||
end | ||
|
||
private | ||
|
||
def bulk_request? | ||
action_name =~ /\Abulk_/ | ||
end | ||
|
||
def bulk_verification_token | ||
session[:_bulk_verification_token] ||= SecureRandom.base64(32) | ||
end | ||
|
||
def bulk_verifier | ||
@_bulk_verifer ||= ActiveSupport::MessageVerifier.new(bulk_verification_token, serializer: JSON) | ||
end | ||
|
||
def selected_ids | ||
@_selected_ids ||= params[:selected_ids].to_s.split(",").map(&:to_i).reject(&:zero?).take(50) | ||
end | ||
|
||
def all_ids | ||
@_all_ids ||= bulk_verifier.verify(params[:all_ids]) | ||
end | ||
|
||
def verify_bulk_request | ||
selected_ids.all?(&method(:verify_bulk_request_id)) | ||
end | ||
|
||
def verify_bulk_request_id(id) | ||
all_ids.include?(id) || raise_bad_request(id) | ||
end | ||
|
||
def raise_bad_request(id) | ||
raise BulkVerification::InvalidBulkRequest, "Invalid bulk request - #{id} not present in #{all_ids.inspect}" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,14 @@ | |
|
||
context "logged in as moderator user" do | ||
let(:user) { FactoryBot.create(:moderator_user) } | ||
before { login_as(user) } | ||
let(:token) { SecureRandom.base64(32) } | ||
let(:verifier) { ActiveSupport::MessageVerifier.new(token, serializer: JSON) } | ||
let(:signature_ids) { verifier.generate([signature.id]) } | ||
|
||
before do | ||
login_as(user) | ||
session[:_bulk_verification_token] = token | ||
end | ||
|
||
describe "GET /admin/signatures" do | ||
before { get :index, q: "Alice" } | ||
|
@@ -168,7 +175,7 @@ | |
before do | ||
expect(Signature).to receive(:find).with([signature.id]).and_return([signature]) | ||
expect(signature).to receive(:validate!).and_return(true) | ||
post :bulk_validate, ids: signature.id, q: "[email protected]" | ||
post :bulk_validate, selected_ids: signature.id, all_ids: signature_ids, q: "[email protected]" | ||
end | ||
|
||
it "redirects to the search page" do | ||
|
@@ -187,7 +194,7 @@ | |
expect(Signature).to receive(:find).with([signature.id]).and_return([signature]) | ||
expect(signature).to receive(:validate!).and_raise(exception) | ||
expect(Appsignal).to receive(:send_exception).with(exception) | ||
post :bulk_validate, ids: signature.id, q: "[email protected]" | ||
post :bulk_validate, selected_ids: signature.id, all_ids: signature_ids, q: "[email protected]" | ||
end | ||
|
||
it "redirects to the search page" do | ||
|
@@ -198,14 +205,38 @@ | |
expect(flash[:alert]).to eq("Signatures could not be validated - please contact support") | ||
end | ||
end | ||
|
||
context "when the signature ids hmac is missing" do | ||
before do | ||
expect(Signature).not_to receive(:find) | ||
end | ||
|
||
it "returns a 400 Bad Request" do | ||
expect { | ||
delete :bulk_validate, selected_ids: signature.id, all_ids: "", q: "[email protected]" | ||
}.to raise_error(BulkVerification::InvalidBulkRequest, /Invalid bulk request for \[\d+\]/) | ||
end | ||
end | ||
|
||
context "when the selected_ids param contains an invalid id" do | ||
before do | ||
expect(Signature).not_to receive(:find) | ||
end | ||
|
||
it "returns a 400 Bad Request" do | ||
expect { | ||
delete :bulk_validate, selected_ids: "1,2", all_ids: signature_ids, q: "[email protected]" | ||
}.to raise_error(BulkVerification::InvalidBulkRequest, /Invalid bulk request - \d+ not present in \[\d+\]/) | ||
end | ||
end | ||
end | ||
|
||
describe "POST /admin/signatures/invalidate" do | ||
context "when the signature is invalidated" do | ||
before do | ||
expect(Signature).to receive(:find).with([signature.id]).and_return([signature]) | ||
expect(signature).to receive(:invalidate!).and_return(true) | ||
post :bulk_invalidate, ids: signature.id, q: "[email protected]" | ||
post :bulk_invalidate, selected_ids: signature.id, all_ids: signature_ids, q: "[email protected]" | ||
end | ||
|
||
it "redirects to the search page" do | ||
|
@@ -224,7 +255,7 @@ | |
expect(Signature).to receive(:find).with([signature.id]).and_return([signature]) | ||
expect(signature).to receive(:invalidate!).and_raise(exception) | ||
expect(Appsignal).to receive(:send_exception).with(exception) | ||
post :bulk_invalidate, ids: signature.id, q: "[email protected]" | ||
post :bulk_invalidate, selected_ids: signature.id, all_ids: signature_ids, q: "[email protected]" | ||
end | ||
|
||
it "redirects to the search page" do | ||
|
@@ -235,14 +266,38 @@ | |
expect(flash[:alert]).to eq("Signatures could not be invalidated - please contact support") | ||
end | ||
end | ||
|
||
context "when the signature ids hmac is missing" do | ||
before do | ||
expect(Signature).not_to receive(:find) | ||
end | ||
|
||
it "returns a 400 Bad Request" do | ||
expect { | ||
delete :bulk_invalidate, selected_ids: signature.id, all_ids: "", q: "[email protected]" | ||
}.to raise_error(BulkVerification::InvalidBulkRequest, /Invalid bulk request for \[\d+\]/) | ||
end | ||
end | ||
|
||
context "when the selected_ids param contains an invalid id" do | ||
before do | ||
expect(Signature).not_to receive(:find) | ||
end | ||
|
||
it "returns a 400 Bad Request" do | ||
expect { | ||
delete :bulk_invalidate, selected_ids: "1,2", all_ids: signature_ids, q: "[email protected]" | ||
}.to raise_error(BulkVerification::InvalidBulkRequest, /Invalid bulk request - \d+ not present in \[\d+\]/) | ||
end | ||
end | ||
end | ||
|
||
describe "DELETE /admin/signatures" do | ||
context "when the signature is destroyed" do | ||
before do | ||
expect(Signature).to receive(:find).with([signature.id]).and_return([signature]) | ||
expect(signature).to receive(:destroy!).and_return(true) | ||
delete :bulk_destroy, ids: signature.id, q: "[email protected]" | ||
delete :bulk_destroy, selected_ids: signature.id, all_ids: signature_ids, q: "[email protected]" | ||
end | ||
|
||
it "redirects to the search page" do | ||
|
@@ -261,7 +316,7 @@ | |
expect(Signature).to receive(:find).with([signature.id]).and_return([signature]) | ||
expect(signature).to receive(:destroy!).and_raise(exception) | ||
expect(Appsignal).to receive(:send_exception).with(exception) | ||
delete :bulk_destroy, ids: signature.id, q: "[email protected]" | ||
delete :bulk_destroy, selected_ids: signature.id, all_ids: signature_ids, q: "[email protected]" | ||
end | ||
|
||
it "redirects to the search page" do | ||
|
@@ -272,6 +327,30 @@ | |
expect(flash[:alert]).to eq("Signatures could not be removed - please contact support") | ||
end | ||
end | ||
|
||
context "when the signature ids hmac is missing" do | ||
before do | ||
expect(Signature).not_to receive(:find) | ||
end | ||
|
||
it "returns a 400 Bad Request" do | ||
expect { | ||
delete :bulk_destroy, selected_ids: signature.id, all_ids: "", q: "[email protected]" | ||
}.to raise_error(BulkVerification::InvalidBulkRequest, /Invalid bulk request for \[\d+\]/) | ||
end | ||
end | ||
|
||
context "when the selected_ids param contains an invalid id" do | ||
before do | ||
expect(Signature).not_to receive(:find) | ||
end | ||
|
||
it "returns a 400 Bad Request" do | ||
expect { | ||
delete :bulk_destroy, selected_ids: "1,2", all_ids: signature_ids, q: "[email protected]" | ||
}.to raise_error(BulkVerification::InvalidBulkRequest, /Invalid bulk request - \d+ not present in \[\d+\]/) | ||
end | ||
end | ||
end | ||
end | ||
end |