Move deleted posts to a protected folder

Set up support for moving deleted files to a protected location
and support for emitting urls with authorization parameters to
permit access to them.

This closes #14
This commit is contained in:
Kira 2019-02-12 03:00:57 -08:00
parent 833f9208df
commit 895fa25c03
5 changed files with 93 additions and 18 deletions

View File

@ -21,7 +21,7 @@ module Moderator
def undelete
@post = ::Post.find(params[:id])
@post.approve!
@post.undelete!
end
def confirm_move_favorites

View File

@ -3,11 +3,15 @@ class StorageManager
DEFAULT_BASE_DIR = "#{Rails.root}/public/data"
attr_reader :base_url, :base_dir, :hierarchical, :tagged_filenames, :large_image_prefix
attr_reader :base_url, :base_dir, :hierarchical, :tagged_filenames, :large_image_prefix, :base_url_protected, :base_dir_protected
def initialize(base_url: default_base_url, base_dir: DEFAULT_BASE_DIR, hierarchical: false, tagged_filenames: Danbooru.config.enable_seo_post_urls, large_image_prefix: Danbooru.config.large_image_prefix)
def initialize(base_url: default_base_url, base_dir: DEFAULT_BASE_DIR, hierarchical: false,
tagged_filenames: Danbooru.config.enable_seo_post_urls, large_image_prefix: Danbooru.config.large_image_prefix,
protected_prefix: Danbooru.config.protected_path_prefix)
@base_url = base_url.chomp("/")
@base_url_protected = "#{@base_url}/#{protected_prefix}"
@base_dir = base_dir
@base_dir_protected = "#{@base_dir}/#{protected_prefix}"
@hierarchical = hierarchical
@tagged_filenames = tagged_filenames
@large_image_prefix = large_image_prefix
@ -48,21 +52,44 @@ class StorageManager
open(file_path(post.md5, post.file_ext, type))
end
def move_file_delete(post)
raise NotImplementedError, "move_file_delete not implemented"
end
def move_file_undelete(post)
raise NotImplementedError, "move_file_undelete not implemented"
end
def protected_params(url, post)
user_id = CurrentUser.id
ip = CurrentUser.ip_addr
time = (Time.now + 15.minute).to_i
secret = Danbooru.config.protected_file_secret
hmac = Digest::MD5.base64digest("#{time} #{url} #{user_id} #{secret}").tr("+/","-_").gsub("==",'')
"?auth=#{hmac}&expires=#{time}&uid=#{user_id}"
end
def file_url(post, type, tagged_filenames: false)
subdir = subdir_for(post.md5)
file = file_name(post.md5, post.file_ext, type)
seo_tags = seo_tags(post) if tagged_filenames
base = post.protect_file? ? base_url_protected : base_url
if type == :preview && !post.has_preview?
url = if type == :preview && !post.has_preview?
"#{root_url}/images/download-preview.png"
elsif type == :preview
"#{base_url}/preview/#{subdir}#{file}"
"#{base}/preview/#{subdir}#{file}"
elsif type == :crop
"#{base_url}/crop/#{subdir}#{file}"
"#{base}/crop/#{subdir}#{file}"
elsif type == :large && post.has_large?
"#{base_url}/sample/#{subdir}#{seo_tags}#{file}"
"#{base}/sample/#{subdir}#{seo_tags}#{file}"
else
"#{base_url}/#{subdir}#{seo_tags}#{post.md5}.#{post.file_ext}"
"#{base}/#{subdir}#{seo_tags}#{post.md5}.#{post.file_ext}"
end
if post.protect_file?
"#{url}#{protected_params(url, post)}" if post.protect_file?
else
url
end
end
@ -72,20 +99,21 @@ class StorageManager
origin
end
def file_path(post_or_md5, file_ext, type)
def file_path(post_or_md5, file_ext, type, protected=false)
md5 = post_or_md5.is_a?(String) ? post_or_md5 : post_or_md5.md5
subdir = subdir_for(md5)
file = file_name(md5, file_ext, type)
base = protected ? base_dir_protected : base_dir
case type
when :preview
"#{base_dir}/preview/#{subdir}#{file}"
"#{base}/preview/#{subdir}#{file}"
when :crop
"#{base_dir}/crop/#{subdir}#{file}"
"#{base}/crop/#{subdir}#{file}"
when :large
"#{base_dir}/sample/#{subdir}#{file}"
"#{base}/sample/#{subdir}#{file}"
when :original
"#{base_dir}/#{subdir}#{file}"
"#{base}/#{subdir}#{file}"
end
end
@ -105,11 +133,7 @@ class StorageManager
end
def subdir_for(md5)
if hierarchical
"#{md5[0..1]}/#{md5[2..3]}/"
else
""
end
hierarchical ? "#{md5[0..1]}/#{md5[2..3]}/" : ""
end
def seo_tags(post)

View File

@ -1,5 +1,6 @@
class StorageManager::Local < StorageManager
DEFAULT_PERMISSIONS = 0644
IMAGE_TYPES = %i[preview large crop original]
def store(io, dest_path)
temp_path = dest_path + "-" + SecureRandom.uuid + ".tmp"
@ -23,4 +24,30 @@ class StorageManager::Local < StorageManager
def open(path)
File.open(path, "r", binmode: true)
end
def move_file_delete(post)
IMAGE_TYPES.each do |type|
path = file_path(post, post.file_ext, type, false)
new_path = file_path(post, post.file_ext, type, true)
move_file(path, new_path)
end
end
def move_file_undelete(post)
IMAGE_TYPES.each do |type|
path = file_path(post, post.file_ext, type, true)
new_path = file_path(post, post.file_ext, type, false)
move_file(path, new_path)
end
end
private
def move_file(old_path, new_path)
if File.exists?(old_path)
FileUtils.mkdir_p(File.dirname(new_path))
FileUtils.mv(old_path, new_path)
FileUtils.chmod(DEFAULT_PERMISSIONS, new_path)
end
end
end

View File

@ -96,6 +96,14 @@ class Post < ApplicationRecord
Post.delete_files(id, md5, file_ext, force: true)
end
def move_files_on_delete
Danbooru.config.storage_manager.move_file_delete(self)
end
def move_files_on_undelete
Danbooru.config.storage_manager.move_file_undelete(self)
end
def distribute_files(file, sample_file, preview_file)
storage_manager.store_file(file, self, :original)
storage_manager.store_file(sample_file, self, :large) if sample_file.present?
@ -1374,6 +1382,10 @@ class Post < ApplicationRecord
end
end
def protect_file?
is_banned || is_deleted
end
def ban!
update_column(:is_banned, true)
ModAction.log("banned post ##{id}",:post_ban)
@ -1400,6 +1412,8 @@ class Post < ApplicationRecord
is_banned: is_banned || options[:ban] || has_tag?("banned_artist")
)
move_files_on_delete
# XXX This must happen *after* the `is_deleted` flag is set to true (issue #3419).
give_favorites_to_parent(options) if options[:move_favorites]
@ -1427,6 +1441,8 @@ class Post < ApplicationRecord
self.approver_id = CurrentUser.id
flags.each {|x| x.resolve!}
save
move_files_on_undelete
approvals.create(user: CurrentUser.user)
ModAction.log("undeleted post ##{id}",:post_undelete)
end

View File

@ -124,6 +124,14 @@ module Danbooru
"sample-"
end
def protected_path_prefix
"deleted"
end
def protected_file_secret
"abc123"
end
# When calculating statistics based on the posts table, gather this many posts to sample from.
def post_sample_size
300