mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 13:45:59 +08:00
d63f1826fe
We want to allow admins to make new required fields apply to existing users. In order for this to work we need to have a way to make those users fill up the fields on their next page load. This is very similar to how adding a 2FA requirement post-fact works. Users will be redirected to a page where they can fill up the remaining required fields, and until they do that they won't be able to do anything else.
223 lines
6.4 KiB
Ruby
223 lines
6.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class UserAvatarsController < ApplicationController
|
|
skip_before_action :preload_json,
|
|
:redirect_to_login_if_required,
|
|
:redirect_to_profile_if_required,
|
|
:check_xhr,
|
|
:verify_authenticity_token,
|
|
only: %i[show show_letter show_proxy_letter]
|
|
|
|
before_action :apply_cdn_headers, only: %i[show show_letter show_proxy_letter]
|
|
|
|
def refresh_gravatar
|
|
user = User.find_by(username_lower: params[:username].downcase)
|
|
guardian.ensure_can_edit!(user)
|
|
|
|
if user
|
|
hijack do
|
|
user.create_user_avatar(user_id: user.id) unless user.user_avatar
|
|
user.user_avatar.update_gravatar!
|
|
|
|
gravatar =
|
|
if user.user_avatar.gravatar_upload_id
|
|
{
|
|
gravatar_upload_id: user.user_avatar.gravatar_upload_id,
|
|
gravatar_avatar_template:
|
|
User.avatar_template(user.username, user.user_avatar.gravatar_upload_id),
|
|
}
|
|
else
|
|
{ gravatar_upload_id: nil, gravatar_avatar_template: nil }
|
|
end
|
|
|
|
render json: gravatar
|
|
end
|
|
else
|
|
raise Discourse::NotFound
|
|
end
|
|
end
|
|
|
|
def show_proxy_letter
|
|
is_asset_path
|
|
|
|
if SiteSetting.external_system_avatars_url !~ %r{\A/letter_avatar_proxy}
|
|
raise Discourse::NotFound
|
|
end
|
|
|
|
params.require(:letter)
|
|
params.require(:color)
|
|
params.require(:version)
|
|
params.require(:size)
|
|
|
|
hijack do
|
|
begin
|
|
proxy_avatar(
|
|
"https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png",
|
|
Time.new(1990, 01, 01),
|
|
)
|
|
rescue OpenURI::HTTPError
|
|
render_blank
|
|
end
|
|
end
|
|
end
|
|
|
|
def show_letter
|
|
is_asset_path
|
|
|
|
params.require(:username)
|
|
params.require(:version)
|
|
params.require(:size)
|
|
|
|
no_cookies
|
|
|
|
return render_blank if params[:version] != LetterAvatar.version
|
|
|
|
hijack do
|
|
image = LetterAvatar.generate(params[:username].to_s, params[:size].to_i)
|
|
|
|
response.headers["Last-Modified"] = File.ctime(image).httpdate
|
|
response.headers["Content-Length"] = File.size(image).to_s
|
|
immutable_for(1.year)
|
|
send_file image, disposition: nil
|
|
end
|
|
end
|
|
|
|
def show
|
|
is_asset_path
|
|
|
|
# we need multisite support to keep a single origin pull for CDNs
|
|
RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
|
|
hijack { show_in_site(params[:hostname]) }
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
def show_in_site(hostname)
|
|
username = params[:username].to_s
|
|
return render_blank unless user = User.find_by(username_lower: username.downcase)
|
|
|
|
upload_id, version = params[:version].split("_")
|
|
|
|
version = (version || OptimizedImage::VERSION).to_i
|
|
|
|
# old versions simply get new avatar
|
|
return render_blank if version > OptimizedImage::VERSION
|
|
|
|
upload_id = upload_id.to_i
|
|
return render_blank if upload_id <= 0
|
|
|
|
size = params[:size].to_i
|
|
return render_blank if size < 8 || size > 1000
|
|
|
|
if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
|
|
closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs }
|
|
avatar_url =
|
|
UserAvatar.local_avatar_url(
|
|
hostname,
|
|
user.encoded_username(lower: true),
|
|
upload_id,
|
|
closest,
|
|
)
|
|
return redirect_to cdn_path(avatar_url), allow_other_host: true
|
|
end
|
|
|
|
upload = Upload.find_by(id: upload_id) if user&.user_avatar&.contains_upload?(upload_id)
|
|
upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id
|
|
|
|
if user.uploaded_avatar && !upload
|
|
avatar_url =
|
|
UserAvatar.local_avatar_url(
|
|
hostname,
|
|
user.encoded_username(lower: true),
|
|
user.uploaded_avatar_id,
|
|
size,
|
|
)
|
|
return redirect_to cdn_path(avatar_url), allow_other_host: true
|
|
elsif upload && optimized = get_optimized_image(upload, size)
|
|
if optimized.local?
|
|
optimized_path = Discourse.store.path_for(optimized)
|
|
image = optimized_path if File.exist?(optimized_path)
|
|
elsif GlobalSetting.redirect_avatar_requests
|
|
return redirect_s3_avatar(Discourse.store.cdn_url(optimized.url))
|
|
else
|
|
return proxy_avatar(Discourse.store.cdn_url(optimized.url), upload.created_at)
|
|
end
|
|
end
|
|
|
|
if image
|
|
response.headers["Last-Modified"] = File.ctime(image).httpdate
|
|
response.headers["Content-Length"] = File.size(image).to_s
|
|
immutable_for 1.year
|
|
send_file image, disposition: nil
|
|
else
|
|
render_blank
|
|
end
|
|
rescue OpenURI::HTTPError
|
|
render_blank
|
|
end
|
|
|
|
# Allow plugins to overwrite max file size value
|
|
def max_file_size
|
|
1.megabyte
|
|
end
|
|
|
|
PROXY_PATH = Rails.root + "tmp/avatar_proxy"
|
|
def proxy_avatar(url, last_modified)
|
|
url = (SiteSetting.force_https ? "https:" : "http:") + url if url[0..1] == "//"
|
|
|
|
sha = Digest::SHA1.hexdigest(url)
|
|
filename = "#{sha}#{File.extname(url)}"
|
|
path = "#{PROXY_PATH}/#{filename}"
|
|
|
|
unless File.exist? path
|
|
FileUtils.mkdir_p PROXY_PATH
|
|
tmp =
|
|
FileHelper.download(
|
|
url,
|
|
max_file_size: max_file_size,
|
|
tmp_file_name: filename,
|
|
follow_redirect: true,
|
|
read_timeout: 10,
|
|
)
|
|
|
|
return render_blank if tmp.nil?
|
|
|
|
FileUtils.mv tmp.path, path
|
|
end
|
|
|
|
response.headers["Last-Modified"] = last_modified.httpdate
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
|
immutable_for(1.year)
|
|
send_file path, disposition: nil
|
|
end
|
|
|
|
def redirect_s3_avatar(url)
|
|
response.cache_control[:max_age] = 1.hour.to_i
|
|
response.cache_control[:public] = true
|
|
response.cache_control[:extras] = ["immutable", "stale-while-revalidate=#{1.day.to_i}"]
|
|
redirect_to url, allow_other_host: true
|
|
end
|
|
|
|
# this protects us from a DoS
|
|
def render_blank
|
|
path = Rails.root + "public/images/avatar.png"
|
|
expires_in 10.minutes, public: true
|
|
response.headers["Last-Modified"] = Time.new(1990, 01, 01).httpdate
|
|
response.headers["Content-Length"] = File.size(path).to_s
|
|
send_file path, disposition: nil
|
|
end
|
|
|
|
protected
|
|
|
|
# consider removal of hacks some time in 2019
|
|
|
|
def get_optimized_image(upload, size)
|
|
return if !upload
|
|
return upload if upload.extension == "svg"
|
|
|
|
upload.get_optimized_image(size, size)
|
|
# TODO decide if we want to detach here
|
|
end
|
|
end
|