Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/controllers/application_controller.rb
- app/controllers/auth/confirmations_controller.rb
- app/controllers/auth/sessions_controller.rb
- app/controllers/settings/deletes_controller.rb
- app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
This commit is contained in:
Thibaut Girka
2019-07-23 10:17:06 +02:00
76 changed files with 662 additions and 590 deletions

View File

@ -8,7 +8,7 @@ class AboutController < ApplicationController
before_action :set_instance_presenter
before_action :set_expires_in
skip_before_action :check_user_permissions, only: [:more, :terms]
skip_before_action :require_functional!, only: [:more, :terms]
def show; end

View File

@ -42,7 +42,7 @@ class AccountsController < ApplicationController
format.json do
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
end
end
end

View File

@ -11,7 +11,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def show
expires_in 3.minutes, public: public_fetch_mode?
render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
end
private

View File

@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController
include RateLimitHeaders
skip_before_action :store_current_location
skip_before_action :check_user_permissions
skip_before_action :require_functional!
before_action :set_cache_headers

View File

@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController
skip_before_action :set_cache_headers
def index
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false).includes(:category), each_serializer: REST::CustomEmojiSerializer)
end
expires_in 3.minutes, public: true
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) }
end
end

View File

@ -7,7 +7,8 @@ class Api::V1::Instances::ActivityController < Api::BaseController
respond_to :json
def show
render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
expires_in 1.day, public: true
render_with_cache json: :activity, expires_in: 1.day
end
private

View File

@ -7,7 +7,8 @@ class Api::V1::Instances::PeersController < Api::BaseController
respond_to :json
def index
render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
expires_in 1.day, public: true
render_with_cache(expires_in: 1.day) { Account.remote.domains }
end
private

View File

@ -5,8 +5,7 @@ class Api::V1::InstancesController < Api::BaseController
skip_before_action :set_cache_headers
def show
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer)
end
expires_in 3.minutes, public: true
render_with_cache json: {}, serializer: REST::InstanceSerializer
end
end

View File

@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base
include Localized
include UserTrackingConcern
include SessionTrackingConcern
include CacheConcern
helper_method :current_account
helper_method :current_session
@ -25,7 +26,7 @@ class ApplicationController < ActionController::Base
rescue_from Mastodon::NotPermittedError, with: :forbidden
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
before_action :check_user_permissions, if: :user_signed_in?
before_action :require_functional!, if: :user_signed_in?
def raise_not_found
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
@ -57,8 +58,8 @@ class ApplicationController < ActionController::Base
forbidden unless current_user&.staff?
end
def check_user_permissions
forbidden if current_user.disabled? || current_user.account.suspended?
def require_functional!
redirect_to edit_user_registration_path unless current_user.functional?
end
def after_sign_out_path_for(_resource_or_scope)
@ -190,52 +191,14 @@ class ApplicationController < ActionController::Base
current_user.setting_skin
end
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes)
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
unless uncached_ids.empty?
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
uncached.each_value do |item|
Rails.cache.write(item, item)
end
end
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
end
def respond_with_error(code)
respond_to do |format|
format.any { head code }
format.html do
set_locale
use_pack 'error'
render "errors/#{code}", layout: 'error', status: code
end
end
end
def render_cached_json(cache_key, **options)
options[:expires_in] ||= 3.minutes
cache_public = options.key?(:public) ? options.delete(:public) : true
content_type = options.delete(:content_type) || 'application/json'
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
yield.to_json
end
expires_in options[:expires_in], public: cache_public
render json: data, content_type: content_type
end
def set_cache_headers
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
end
end

View File

@ -4,20 +4,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
before_action :set_body_classes
before_action :set_user, only: [:finish_signup]
before_action :set_pack
def finish_signup
return unless request.patch? && params[:user]
if @user.update(user_params)
@user.skip_reconfirmation!
bypass_sign_in(@user)
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
else
@show_errors = true
end
end
skip_before_action :require_functional!
private
@ -25,18 +14,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
use_pack 'auth'
end
def set_user
@user = current_user
end
def set_body_classes
@body_classes = 'lighter'
end
def user_params
params.require(:user).permit(:email)
end
def after_confirmation_path_for(_resource_name, user)
if user.created_by_application && truthy_param?(:redirect_to_app)
user.created_by_application.redirect_uri

View File

@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
if resource.email_verified?
root_path
else
finish_signup_path
auth_setup_path(missing_email: '1')
end
end
end

View File

@ -10,6 +10,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_sessions, only: [:edit, :update]
before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update]
skip_before_action :require_functional!, only: [:edit, :update]
def new
super(&:build_invite_request)
@ -44,7 +47,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def after_sign_up_path_for(_resource)
new_user_session_path
auth_setup_path
end
def after_sign_in_path_for(_resource)
@ -107,4 +110,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def set_sessions
@sessions = current_user.session_activations
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@ -6,9 +6,11 @@ class Auth::SessionsController < Devise::SessionsController
layout 'auth'
skip_before_action :require_no_authentication, only: [:create]
skip_before_action :check_user_permissions, only: [:destroy]
skip_before_action :require_functional!
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
prepend_before_action :set_pack
before_action :set_instance_presenter, only: [:new]
before_action :set_body_classes

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
class Auth::SetupController < ApplicationController
layout 'auth'
before_action :authenticate_user!
before_action :require_unconfirmed_or_pending!
before_action :set_body_classes
before_action :set_user
skip_before_action :require_functional!
def show
flash.now[:notice] = begin
if @user.pending?
I18n.t('devise.registrations.signed_up_but_pending')
else
I18n.t('devise.registrations.signed_up_but_unconfirmed')
end
end
end
def update
# This allows updating the e-mail without entering a password as is required
# on the account settings page; however, we only allow this for accounts
# that were not confirmed yet
if @user.update(user_params)
redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
else
render :show
end
end
helper_method :missing_email?
private
def require_unconfirmed_or_pending!
redirect_to root_path if current_user.confirmed? && current_user.approved?
end
def set_user
@user = current_user
end
def set_body_classes
@body_classes = 'lighter'
end
def user_params
params.require(:user).permit(:email)
end
def missing_email?
truthy_param?(:missing_email)
end
end

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
module CacheConcern
extend ActiveSupport::Concern
def render_with_cache(**options)
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
expires_in = options.delete(:expires_in) || 3.minutes
body = Rails.cache.read(key, raw: true)
if body
render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body))
else
if block_given?
options[:json] = yield
elsif options[:json].is_a?(Symbol)
options[:json] = send(options[:json])
end
render(options)
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
end
end
def set_cache_headers
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
end
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes)
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
unless uncached_ids.empty?
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
uncached.each_value do |item|
Rails.cache.write(item, item)
end
end
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
end
end

View File

@ -4,16 +4,19 @@ module Localized
extend ActiveSupport::Concern
included do
before_action :set_locale
around_action :set_locale
end
private
def set_locale
I18n.locale = default_locale
I18n.locale = current_user.locale if user_signed_in?
rescue I18n::InvalidLocale
I18n.locale = default_locale
locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
locale ||= session[:locale] ||= default_locale
locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
I18n.with_locale(locale) do
yield
end
end
def default_locale

View File

@ -8,7 +8,7 @@ class EmojisController < ApplicationController
respond_to do |format|
format.json do
expires_in 3.minutes, public: true
render json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
end
end
end

View File

@ -8,6 +8,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :set_pack
before_action :set_body_classes
skip_before_action :require_functional!
include Localized
def destroy

View File

@ -3,6 +3,9 @@
class Settings::DeletesController < Settings::BaseController
prepend_before_action :check_enabled_deletion
before_action :require_not_suspended!
skip_before_action :require_functional!
def show
@confirmation = Form::DeleteConfirmation.new
@ -27,4 +30,8 @@ class Settings::DeletesController < Settings::BaseController
def delete_params
params.require(:form_delete_confirmation).permit(:password)
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end

View File

@ -5,6 +5,8 @@ class Settings::SessionsController < ApplicationController
before_action :authenticate_user!
before_action :set_session, only: :destroy
skip_before_action :require_functional!
def destroy
@session.destroy!
flash[:notice] = I18n.t('sessions.revoke_success')

View File

@ -5,6 +5,8 @@ module Settings
class ConfirmationsController < BaseController
before_action :ensure_otp_secret
skip_before_action :require_functional!
def new
prepare_two_factor_form
end

View File

@ -3,6 +3,8 @@
module Settings
module TwoFactorAuthentication
class RecoveryCodesController < BaseController
skip_before_action :require_functional!
def create
@recovery_codes = current_user.generate_otp_backup_codes!
current_user.save!

View File

@ -4,6 +4,8 @@ module Settings
class TwoFactorAuthenticationsController < BaseController
before_action :verify_otp_required, only: [:create]
skip_before_action :require_functional!
def show
@confirmation = Form::TwoFactorConfirmation.new
end

View File

@ -34,14 +34,14 @@ class StatusesController < ApplicationController
format.json do
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
end
end
end
def activity
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
end
def embed