Unverified Commit 98a2603d authored by Takeshi Umeda's avatar Takeshi Umeda Committed by GitHub
Browse files

Merge branch 'master' into feature-limited-visibility-bearcaps

parents 7cd4ed7d 087ed843
Showing with 139 additions and 57 deletions
+139 -57
......@@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, with: :service_unavailable
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, with: :service_unavailable
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
......
......@@ -2,6 +2,7 @@
class Auth::RegistrationsController < Devise::RegistrationsController
include Devise::Controllers::Rememberable
include RegistrationSpamConcern
layout :determine_layout
......@@ -13,6 +14,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update]
before_action :set_cache_headers, only: [:edit, :update]
before_action :set_registration_form_time, only: :new
skip_before_action :require_functional!, only: [:edit, :update]
......@@ -45,16 +47,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def build_resource(hash = nil)
super(hash)
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.current_sign_in_ip = request.remote_ip
resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.registration_form_time = session[:registration_form_time]
resource.sign_up_ip = request.remote_ip
resource.build_account if resource.account.nil?
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u|
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement)
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
end
end
......
......@@ -7,6 +7,7 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
include TwoFactorAuthenticationConcern
include SignInTokenAuthenticationConcern
......@@ -24,6 +25,7 @@ class Auth::SessionsController < Devise::SessionsController
def create
super do |resource|
resource.update_sign_in!(request, new_sign_in: true)
remember_me(resource)
flash.delete(:notice)
end
......@@ -57,7 +59,7 @@ class Auth::SessionsController < Devise::SessionsController
def find_user
if session[:attempt_user_id]
User.find(session[:attempt_user_id])
User.find_by(id: session[:attempt_user_id])
else
user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
......@@ -90,6 +92,7 @@ class Auth::SessionsController < Devise::SessionsController
def require_no_authentication
super
# Delete flash message that isn't entirely useful and may be confusing in
# most cases because /web doesn't display/clear flash messages.
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
......@@ -107,13 +110,30 @@ class Auth::SessionsController < Devise::SessionsController
def home_paths(resource)
paths = [about_path]
if single_user_mode? && resource.is_a?(User)
paths << short_account_path(username: resource.account)
end
paths
end
def continue_after?
truthy_param?(:continue)
end
def restart_session
clear_attempt_from_session
redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout')
end
def set_attempt_session(user)
session[:attempt_user_id] = user.id
session[:attempt_user_updated_at] = user.updated_at.to_s
end
def clear_attempt_from_session
session.delete(:attempt_user_id)
session.delete(:attempt_user_updated_at)
end
end
......@@ -29,6 +29,24 @@ module AccountOwnedConcern
end
def check_account_suspension
expires_in(3.minutes, public: true) && gone if @account.suspended?
if @account.suspended_permanently?
permanent_suspension_response
elsif @account.suspended? && !skip_temporary_suspension_response?
temporary_suspension_response
end
end
def skip_temporary_suspension_response?
false
end
def permanent_suspension_response
expires_in(3.minutes, public: true)
gone
end
def temporary_suspension_response
expires_in(3.minutes, public: true)
forbidden
end
end
......@@ -45,10 +45,10 @@ module CacheConcern
end
end
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
raw.filter_map { |item| cached_keys_with_value[item.id] || uncached[item.id] }
end
def cache_collection_paginated_by_id(raw, klass, limit, options)
cache_collection raw.cache_ids.paginate_by_id(limit, options), klass
cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass
end
end
......@@ -32,7 +32,6 @@ module ChallengableConcern
if params.key?(:form_challenge)
if challenge_passed?
session[:challenge_passed_at] = Time.now.utc
return
else
flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge
......
......@@ -5,7 +5,6 @@ module ExportControllerConcern
included do
before_action :authenticate_user!
before_action :require_not_suspended!
before_action :load_export
skip_before_action :require_functional!
......@@ -30,8 +29,4 @@ module ExportControllerConcern
def export_filename
"#{controller_name}.csv"
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end
# frozen_string_literal: true
module RegistrationSpamConcern
extend ActiveSupport::Concern
def set_registration_form_time
session[:registration_form_time] = Time.now.utc
end
end
......@@ -18,7 +18,9 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token
user = self.resource = find_user
if user_params[:sign_in_token_attempt].present? && session[:attempt_user_id]
if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
restart_session
elsif user_params.key?(:sign_in_token_attempt) && session[:attempt_user_id]
authenticate_with_sign_in_token_attempt(user)
elsif user.present? && user.external_or_valid_password?(user_params[:password])
prompt_for_sign_in_token(user)
......@@ -27,7 +29,7 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token_attempt(user)
if valid_sign_in_token_attempt?(user)
session.delete(:attempt_user_id)
clear_attempt_from_session
remember_me(user)
sign_in(user)
else
......@@ -42,10 +44,10 @@ module SignInTokenAuthenticationConcern
UserMailer.sign_in_token(user, request.remote_ip, request.user_agent, Time.now.utc.to_s).deliver_later!
end
set_locale do
session[:attempt_user_id] = user.id
@body_classes = 'lighter'
render :sign_in_token
end
set_attempt_session(user)
@body_classes = 'lighter'
set_locale { render :sign_in_token }
end
end
......@@ -76,6 +76,7 @@ module SignatureVerification
raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
verify_signature_strength!
verify_body_digest!
account = account_from_key_id(signature_params['keyId'])
......@@ -126,12 +127,21 @@ module SignatureVerification
def verify_signature_strength!
raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest')
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed' unless signed_headers.include?('host')
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
end
def verify_body_digest!
return unless signed_headers.include?('digest')
digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
sha256 = digests.assoc('sha-256')
raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" if body_digest != sha256[1]
end
def verify_signature(account, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
end
......@@ -153,8 +163,6 @@ module SignatureVerification
raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
"(expires): #{signature_params['expires']}"
elsif signed_header == 'digest'
"digest: #{body_digest}"
else
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
end
......@@ -187,7 +195,7 @@ module SignatureVerification
end
def body_digest
"SHA-256=#{Digest::SHA256.base64digest(request_body)}"
@body_digest ||= Digest::SHA256.base64digest(request_body)
end
def to_header_name(name)
......
......@@ -37,9 +37,11 @@ module TwoFactorAuthenticationConcern
def authenticate_with_two_factor
user = self.resource = find_user
if user.webauthn_enabled? && user_params[:credential].present? && session[:attempt_user_id]
if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
restart_session
elsif user.webauthn_enabled? && user_params.key?(:credential) && session[:attempt_user_id]
authenticate_with_two_factor_via_webauthn(user)
elsif user_params[:otp_attempt].present? && session[:attempt_user_id]
elsif user_params.key?(:otp_attempt) && session[:attempt_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user.present? && user.external_or_valid_password?(user_params[:password])
prompt_for_two_factor(user)
......@@ -50,7 +52,7 @@ module TwoFactorAuthenticationConcern
webauthn_credential = WebAuthn::Credential.from_get(user_params[:credential])
if valid_webauthn_credential?(user, webauthn_credential)
session.delete(:attempt_user_id)
clear_attempt_from_session
remember_me(user)
sign_in(user)
render json: { redirect_path: root_path }, status: :ok
......@@ -61,7 +63,7 @@ module TwoFactorAuthenticationConcern
def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user)
session.delete(:attempt_user_id)
clear_attempt_from_session
remember_me(user)
sign_in(user)
else
......@@ -71,16 +73,18 @@ module TwoFactorAuthenticationConcern
end
def prompt_for_two_factor(user)
set_locale do
session[:attempt_user_id] = user.id
@body_classes = 'lighter'
@webauthn_enabled = user.webauthn_enabled?
@scheme_type = if user.webauthn_enabled? && user_params[:otp_attempt].blank?
'webauthn'
else
'totp'
end
render :two_factor
set_attempt_session(user)
@body_classes = 'lighter'
@webauthn_enabled = user.webauthn_enabled?
@scheme_type = begin
if user.webauthn_enabled? && user_params[:otp_attempt].blank?
'webauthn'
else
'totp'
end
end
set_locale { render :two_factor }
end
end
......@@ -6,14 +6,13 @@ module UserTrackingConcern
UPDATE_SIGN_IN_HOURS = 24
included do
before_action :set_user_activity
before_action :update_user_sign_in
end
private
def set_user_activity
return unless user_needs_sign_in_update?
current_user.update_tracked_fields!(request)
def update_user_sign_in
current_user.update_sign_in!(request) if user_needs_sign_in_update?
end
def user_needs_sign_in_update?
......
......@@ -9,7 +9,7 @@ class FiltersController < ApplicationController
before_action :set_body_classes
def index
@filters = current_account.custom_filters
@filters = current_account.custom_filters.order(:phrase)
end
def new
......
......@@ -52,6 +52,14 @@ class FollowerAccountsController < ApplicationController
account_followers_url(@account, page: page) unless page.nil?
end
def next_page_url
page_url(follows.next_page) if follows.respond_to?(:next_page)
end
def prev_page_url
page_url(follows.prev_page) if follows.respond_to?(:prev_page)
end
def collection_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
......@@ -60,8 +68,8 @@ class FollowerAccountsController < ApplicationController
size: @account.followers_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
part_of: account_followers_url(@account),
next: page_url(follows.next_page),
prev: page_url(follows.prev_page)
next: next_page_url,
prev: prev_page_url
)
else
ActivityPub::CollectionPresenter.new(
......
......@@ -52,6 +52,14 @@ class FollowingAccountsController < ApplicationController
account_following_index_url(@account, page: page) unless page.nil?
end
def next_page_url
page_url(follows.next_page) if follows.respond_to?(:next_page)
end
def prev_page_url
page_url(follows.prev_page) if follows.respond_to?(:prev_page)
end
def collection_presenter
if page_requested?
ActivityPub::CollectionPresenter.new(
......@@ -60,8 +68,8 @@ class FollowingAccountsController < ApplicationController
size: @account.following_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
part_of: account_following_index_url(@account),
next: page_url(follows.next_page),
prev: page_url(follows.prev_page)
next: next_page_url,
prev: prev_page_url
)
else
ActivityPub::CollectionPresenter.new(
......
......@@ -17,6 +17,6 @@ class InstanceActorsController < ApplicationController
end
def restrict_fields_to
%i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
%i(id type preferred_username inbox outbox public_key endpoints url manually_approves_followers)
end
end
......@@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :store_current_location
before_action :authenticate_resource_owner!
before_action :require_not_suspended!, only: :destroy
before_action :set_body_classes
skip_before_action :require_functional!
......@@ -25,4 +26,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def store_current_location
store_location_for(:user, request.url)
end
def require_not_suspended!
forbidden if current_account.suspended?
end
end
......@@ -5,6 +5,7 @@ class RelationshipsController < ApplicationController
before_action :authenticate_user!
before_action :set_accounts, only: :show
before_action :set_relationships, only: :show
before_action :set_body_classes
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
......@@ -28,6 +29,10 @@ class RelationshipsController < ApplicationController
@accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
end
def set_relationships
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
end
def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: [])
end
......@@ -49,7 +54,9 @@ class RelationshipsController < ApplicationController
end
def action_from_button
if params[:unfollow]
if params[:follow]
'follow'
elsif params[:unfollow]
'unfollow'
elsif params[:remove_from_followers]
'remove_from_followers'
......
# frozen_string_literal: true
class Settings::AliasesController < Settings::BaseController
layout 'admin'
skip_before_action :require_functional!
before_action :authenticate_user!
before_action :require_not_suspended!
before_action :set_aliases, except: :destroy
before_action :set_alias, only: :destroy
......
# frozen_string_literal: true
class Settings::ApplicationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update]
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment