-
Notifications
You must be signed in to change notification settings - Fork 81
Authorize revision history and markdown uploads against current book access #463
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ class ActionText::Markdown::UploadsControllerTest < ActionDispatch::IntegrationT | |
| test "attach a file" do | ||
| assert_changes -> { ActiveStorage::Attachment.count }, 1 do | ||
| post action_text_markdown_uploads_url, params: { | ||
| record_gid: pages(:welcome).to_signed_global_id.to_s, | ||
| record_gid: uploads_signed_id_for(pages(:welcome)), | ||
| attribute_name: "body", | ||
| file: fixture_file_upload("reading.webp", "image/webp") | ||
| }, as: :xhr | ||
|
|
@@ -20,15 +20,139 @@ class ActionText::Markdown::UploadsControllerTest < ActionDispatch::IntegrationT | |
| assert JSON.parse(response.body)["fileUrl"].start_with?("/") | ||
| end | ||
|
|
||
| test "view attached file" do | ||
| markdown = pages(:welcome).body.tap(&:save!) | ||
| markdown.uploads.attach fixture_file_upload("reading.webp", "image/webp") | ||
| test "a signed id minted for some other purpose can't be used to upload" do | ||
| assert_no_changes -> { ActiveStorage::Attachment.count } do | ||
| post action_text_markdown_uploads_url, params: { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The newly added requests here and throughout both changed controller test files use AGENTS.md reference: AGENTS.md:L8-L9 Useful? React with 👍 / 👎. |
||
| record_gid: pages(:welcome).to_signed_global_id.to_s, | ||
| attribute_name: "body", | ||
| file: fixture_file_upload("reading.webp", "image/webp") | ||
| }, as: :xhr | ||
| end | ||
|
|
||
| assert_response :not_found | ||
| end | ||
|
|
||
| test "an expired signed id can't be used to upload" do | ||
| record_gid = uploads_signed_id_for(pages(:welcome)) | ||
|
|
||
| travel ActionText::Markdown::UPLOADS_SIGNED_ID_EXPIRY + 1.hour do | ||
| assert_no_changes -> { ActiveStorage::Attachment.count } do | ||
| post action_text_markdown_uploads_url, params: { | ||
| record_gid: record_gid, | ||
| attribute_name: "body", | ||
| file: fixture_file_upload("reading.webp", "image/webp") | ||
| }, as: :xhr | ||
| end | ||
| end | ||
|
|
||
| assert_response :not_found | ||
| end | ||
|
|
||
| test "a revoked collaborator can't upload with a signed id minted while an editor" do | ||
| record_gid = uploads_signed_id_for(pages(:welcome)) | ||
| accesses(:kevin_handbook).destroy! | ||
|
|
||
| assert_no_changes -> { ActiveStorage::Attachment.count } do | ||
| post action_text_markdown_uploads_url, params: { | ||
| record_gid: record_gid, | ||
| attribute_name: "body", | ||
| file: fixture_file_upload("reading.webp", "image/webp") | ||
| }, as: :xhr | ||
| end | ||
|
|
||
| assert_response :not_found | ||
| end | ||
|
|
||
| test "a downgraded editor can't upload" do | ||
| record_gid = uploads_signed_id_for(pages(:welcome)) | ||
| accesses(:kevin_handbook).update! level: :reader | ||
|
|
||
| assert_no_changes -> { ActiveStorage::Attachment.count } do | ||
| post action_text_markdown_uploads_url, params: { | ||
| record_gid: record_gid, | ||
| attribute_name: "body", | ||
| file: fixture_file_upload("reading.webp", "image/webp") | ||
| }, as: :xhr | ||
| end | ||
|
|
||
| assert_response :forbidden | ||
| end | ||
|
|
||
| test "a reader can't upload" do | ||
| sign_in :jz | ||
|
|
||
| attachment = pages(:welcome).body.uploads.last | ||
| assert_no_changes -> { ActiveStorage::Attachment.count } do | ||
| post action_text_markdown_uploads_url, params: { | ||
| record_gid: uploads_signed_id_for(pages(:welcome)), | ||
| attribute_name: "body", | ||
| file: fixture_file_upload("reading.webp", "image/webp") | ||
| }, as: :xhr | ||
| end | ||
|
|
||
| assert_response :forbidden | ||
| end | ||
|
|
||
| test "view attached file" do | ||
| books(:handbook).update! published: true | ||
| attachment = attach_upload_to_welcome_page | ||
|
|
||
| get action_text_markdown_upload_url(slug: attachment.slug) | ||
|
|
||
| assert_response :redirect | ||
| assert_match /\/rails\/active_storage\/.*\/reading\.webp/, @response.redirect_url | ||
| end | ||
|
|
||
| test "an attachment of a published book is publicly cacheable" do | ||
| books(:handbook).update! published: true | ||
| attachment = attach_upload_to_welcome_page | ||
|
|
||
| get action_text_markdown_upload_url(slug: attachment.slug) | ||
|
|
||
| assert_match "public", @response.headers["Cache-Control"] | ||
| end | ||
|
|
||
| test "an attachment of an unpublished book is not served to anonymous clients" do | ||
| books(:handbook).update! published: false | ||
| attachment = attach_upload_to_welcome_page | ||
|
|
||
| reset! | ||
| get action_text_markdown_upload_url(slug: attachment.slug) | ||
|
|
||
| assert_response :not_found | ||
| end | ||
|
|
||
| test "an attachment of an unpublished book is not served to a user without access" do | ||
| books(:handbook).update! published: false | ||
| attachment = attach_upload_to_welcome_page | ||
|
|
||
| accesses(:kevin_handbook).destroy! | ||
| get action_text_markdown_upload_url(slug: attachment.slug) | ||
|
|
||
| assert_response :not_found | ||
| end | ||
|
|
||
| test "an attachment of an unpublished book is served to a reader, but not publicly cached" do | ||
| books(:handbook).update! published: false | ||
| attachment = attach_upload_to_welcome_page | ||
|
|
||
| sign_in :jz | ||
| get action_text_markdown_upload_url(slug: attachment.slug) | ||
|
|
||
| assert_response :redirect | ||
| assert_no_match "public", @response.headers["Cache-Control"].to_s | ||
| end | ||
|
|
||
| private | ||
| def uploads_signed_id_for(record) | ||
| record.to_signed_global_id( | ||
| expires_in: ActionText::Markdown::UPLOADS_SIGNED_ID_EXPIRY, | ||
| for: ActionText::Markdown::UPLOADS_SIGNED_ID_PURPOSE | ||
| ).to_s | ||
| end | ||
|
|
||
| def attach_upload_to_welcome_page | ||
| markdown = pages(:welcome).body.tap(&:save!) | ||
| markdown.uploads.attach fixture_file_upload("reading.webp", "image/webp") | ||
| pages(:welcome).body.uploads.last | ||
| end | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For unpublished books in the inspected production configuration, this only marks the
/u/...redirect private;config/environments/production.rb:28selects the:localservice,config/storage.yml:5-8configures that Disk service aspublic: true, andconfig/initializers/active_storage.rb:1-4publicly caches the final Disk response for a year. Consequently, once an authorized reader follows this redirect, its permanent unauthenticated target can be reused or shared after access is revoked, bypassing the new check. Private uploads need authenticated/proxied delivery of the actual bytes rather than only a private redirect.Useful? React with 👍 / 👎.