Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 9 additions & 0 deletions app/controllers/concerns/active_storage_authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ActiveStorageAuthentication
extend ActiveSupport::Concern
include Authentication::SessionLookup

private
def require_active_storage_authentication
head :unauthorized unless find_session_by_cookie
end
end
16 changes: 16 additions & 0 deletions config/initializers/active_storage_authentication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Active Storage mounts its direct-upload write endpoints
# (POST /rails/active_storage/direct_uploads and the disk-service PUT) on
# framework controllers that inherit from ActiveStorage::BaseController, so
# they never pass through ApplicationController's require_authentication.
# Writebook uploads attachments through ActionText::Markdown::UploadsController
# as an ordinary multipart POST and does not use direct uploads at all, leaving
# these endpoints reachable by anyone who can read a public page. Require a
# valid Writebook session before an anonymous caller can allocate a Blob or
# persist bytes to disk.
Rails.application.config.to_prepare do
ActiveStorage::DirectUploadsController.include ActiveStorageAuthentication
ActiveStorage::DirectUploadsController.before_action :require_active_storage_authentication

ActiveStorage::DiskController.include ActiveStorageAuthentication
ActiveStorage::DiskController.before_action :require_active_storage_authentication, only: :update
end
67 changes: 67 additions & 0 deletions test/controllers/active_storage_authentication_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require "test_helper"

class ActiveStorageAuthenticationTest < ActionDispatch::IntegrationTest
test "direct upload metadata endpoint rejects anonymous callers" do
get new_session_url
Comment thread
jeremy marked this conversation as resolved.
Outdated
assert_response :success

assert_no_difference -> { ActiveStorage::Blob.count } do
post rails_direct_uploads_url, params: blob_params, as: :json
Comment thread
jeremy marked this conversation as resolved.
Outdated
end

assert_response :unauthorized
end

test "direct upload metadata endpoint allows authenticated users" do
sign_in :david

assert_difference -> { ActiveStorage::Blob.count }, 1 do
post rails_direct_uploads_url, params: blob_params, as: :json
end

assert_response :success
end

test "disk service upload endpoint rejects anonymous callers" do
sign_in :david
post rails_direct_uploads_url, params: blob_params, as: :json
assert_response :success
upload_path = URI.parse(response.parsed_body.dig("direct_upload", "url")).request_uri

anonymous = open_session
anonymous.put upload_path,
params: attachment_bytes,
headers: { "Content-Type" => "application/octet-stream" }

assert_equal 401, anonymous.status
Comment thread
jeremy marked this conversation as resolved.
end

test "disk service download endpoint stays public" do
ActiveStorage::Current.url_options = { host: "www.example.com", protocol: "https" }
blob = ActiveStorage::Blob.create_and_upload! \
io: StringIO.new(attachment_bytes), filename: "hi.txt", content_type: "text/plain"
Comment thread
jeremy marked this conversation as resolved.
download_path = URI.parse(blob.url).request_uri

anonymous = open_session
anonymous.get download_path

assert_equal 200, anonymous.status
assert_equal attachment_bytes, anonymous.response.body
ensure
blob&.purge
end

private
def attachment_bytes
"hello!"
end

def blob_params
{ blob: {
filename: "quota.bin",
byte_size: attachment_bytes.bytesize,
checksum: Digest::MD5.base64digest(attachment_bytes),
content_type: "application/octet-stream"
} }
end
end
Loading