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

Conflicts:
- Gemfile
- Gemfile.lock
- app/controllers/about_controller.rb
- app/controllers/auth/sessions_controller.rb
This commit is contained in:
Thibaut Girka
2019-09-30 12:23:57 +02:00
352 changed files with 7151 additions and 2269 deletions

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_aliases
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# acct :string default(""), not null
# uri :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountAlias < ApplicationRecord
belongs_to :account
validates :acct, presence: true, domain: { acct: true }
validates :uri, presence: true
validates :uri, uniqueness: { scope: :account_id }
before_validation :set_uri
after_create :add_to_account
after_destroy :remove_from_account
def acct=(val)
val = val.to_s.strip
super(val.start_with?('@') ? val[1..-1] : val)
end
private
def set_uri
target_account = ResolveAccountService.new.call(acct)
self.uri = ActivityPub::TagManager.instance.uri_for(target_account) unless target_account.nil?
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
# Validation will take care of it
end
def add_to_account
account.update(also_known_as: account.also_known_as + [uri])
end
def remove_from_account
account.update(also_known_as: account.also_known_as.reject { |x| x == uri })
end
end

View File

@ -0,0 +1,78 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_migrations
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# acct :string default(""), not null
# followers_count :bigint(8) default(0), not null
# target_account_id :bigint(8)
# created_at :datetime not null
# updated_at :datetime not null
#
class AccountMigration < ApplicationRecord
COOLDOWN_PERIOD = 30.days.freeze
belongs_to :account
belongs_to :target_account, class_name: 'Account'
before_validation :set_target_account
before_validation :set_followers_count
validates :acct, presence: true, domain: { acct: true }
validate :validate_migration_cooldown
validate :validate_target_account
scope :within_cooldown, ->(now = Time.now.utc) { where(arel_table[:created_at].gteq(now - COOLDOWN_PERIOD)) }
attr_accessor :current_password, :current_username
def save_with_challenge(current_user)
if current_user.encrypted_password.present?
errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
else
errors.add(:current_username, :invalid) unless account.username == current_username
end
return false unless errors.empty?
save
end
def cooldown_at
created_at + COOLDOWN_PERIOD
end
def acct=(val)
super(val.to_s.strip.gsub(/\A@/, ''))
end
private
def set_target_account
self.target_account = ResolveAccountService.new.call(acct)
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
# Validation will take care of it
end
def set_followers_count
self.followers_count = account.followers_count
end
def validate_target_account
if target_account.nil?
errors.add(:acct, I18n.t('migrations.errors.not_found'))
else
errors.add(:acct, I18n.t('migrations.errors.missing_also_known_as')) unless target_account.also_known_as.include?(ActivityPub::TagManager.instance.uri_for(account))
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
end
end
def validate_migration_cooldown
errors.add(:base, I18n.t('migrations.errors.on_cooldown')) if account.migrations.within_cooldown.exists?
end
end

View File

@ -53,6 +53,8 @@ module AccountAssociations
# Account migrations
belongs_to :moved_to_account, class_name: 'Account', optional: true
has_many :migrations, class_name: 'AccountMigration', dependent: :destroy, inverse_of: :account
has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account
# Hashtags
has_and_belongs_to_many :tags

View File

@ -3,24 +3,50 @@
module LdapAuthenticable
extend ActiveSupport::Concern
def ldap_setup(_attributes)
self.confirmed_at = Time.now.utc
self.admin = false
self.external = true
save!
end
class_methods do
def authenticate_with_ldap(params = {})
ldap = Net::LDAP.new(ldap_options)
filter = format(Devise.ldap_search_filter, uid: Devise.ldap_uid, email: params[:email])
if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: filter, password: params[:password]))
ldap_get_user(user_info.first)
end
end
def ldap_get_user(attributes = {})
resource = joins(:account).find_by(accounts: { username: attributes[Devise.ldap_uid.to_sym].first })
if resource.blank?
resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first })
resource.ldap_setup(attributes)
resource = new(email: attributes[:mail].first, agreement: true, account_attributes: { username: attributes[Devise.ldap_uid.to_sym].first }, admin: false, external: true, confirmed_at: Time.now.utc)
resource.save!
end
resource
end
def ldap_options
opts = {
host: Devise.ldap_host,
port: Devise.ldap_port,
base: Devise.ldap_base,
auth: {
method: :simple,
username: Devise.ldap_bind_dn,
password: Devise.ldap_password,
},
connect_timeout: 10,
}
if [:simple_tls, :start_tls].include?(Devise.ldap_method)
opts[:encryption] = {
method: Devise.ldap_method,
tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.tap { |options| options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if Devise.ldap_tls_no_verify },
}
end
opts
end
end
end

View File

@ -26,6 +26,7 @@ class DomainBlock < ApplicationRecord
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
scope :with_user_facing_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)) }
scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), reject_media, domain')) }
class << self
def suspend?(domain)

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
class Form::Challenge
include ActiveModel::Model
attr_accessor :current_password, :current_username,
:return_to
end

View File

@ -1,25 +0,0 @@
# frozen_string_literal: true
class Form::Migration
include ActiveModel::Validations
attr_accessor :acct, :account
def initialize(attrs = {})
@account = attrs[:account]
@acct = attrs[:account].acct unless @account.nil?
@acct = attrs[:acct].gsub(/\A@/, '').strip unless attrs[:acct].nil?
end
def valid?
return false unless super
set_account
errors.empty?
end
private
def set_account
self.account = (ResolveAccountService.new.call(acct) if account.nil? && acct.present?)
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
class Form::Redirect
include ActiveModel::Model
attr_accessor :account, :target_account, :current_password,
:current_username
attr_reader :acct
validates :acct, presence: true, domain: { acct: true }
validate :validate_target_account
def valid_with_challenge?(current_user)
if current_user.encrypted_password.present?
errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password)
else
errors.add(:current_username, :invalid) unless account.username == current_username
end
return false unless errors.empty?
set_target_account
valid?
end
def acct=(val)
@acct = val.to_s.strip.gsub(/\A@/, '')
end
private
def set_target_account
@target_account = ResolveAccountService.new.call(acct)
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error
# Validation will take care of it
end
def validate_target_account
if target_account.nil?
errors.add(:acct, I18n.t('migrations.errors.not_found'))
else
errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id
errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id
end
end
end

View File

@ -16,6 +16,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# lock_version :integer default(0), not null
# voters_count :bigint(8)
#
class Poll < ApplicationRecord
@ -54,6 +55,10 @@ class Poll < ApplicationRecord
account.id == account_id || votes.where(account: account).exists?
end
def own_votes(account)
votes.where(account: account).pluck(:choice)
end
delegate :local?, to: :account
def remote?

View File

@ -47,6 +47,10 @@ class PreviewCard < ApplicationRecord
before_save :extract_dimensions, if: :link?
def missing_image?
width.present? && height.present? && image_file_name.blank?
end
def save_with_optional_image!
save!
rescue ActiveRecord::RecordInvalid

View File

@ -12,8 +12,6 @@
#
class Relay < ApplicationRecord
PRESET_RELAY = 'https://relay.joinmastodon.org/inbox'
validates :inbox_url, presence: true, uniqueness: true, url: true, if: :will_save_change_to_inbox_url?
enum state: [:idle, :pending, :accepted, :rejected]
@ -74,7 +72,6 @@ class Relay < ApplicationRecord
end
def ensure_disabled
return unless enabled?
disable!
disable! if enabled?
end
end

View File

@ -49,7 +49,7 @@ class RemoteFollow
end
def fetch_template!
return missing_resource if acct.blank?
return missing_resource_error if acct.blank?
_, domain = acct.split('@')

View File

@ -360,7 +360,7 @@ class Status < ApplicationRecord
end
def reblogs_map(status_ids, account_id)
select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
unscoped.select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
end
def mutes_map(conversation_ids, account_id)

View File

@ -124,16 +124,15 @@ class Tag < ApplicationRecord
end
end
def search_for(term, limit = 5, offset = 0)
def search_for(term, limit = 5, offset = 0, options = {})
normalized_term = normalize(term.strip).mb_chars.downcase.to_s
pattern = sanitize_sql_like(normalized_term) + '%'
query = Tag.listable.where(arel_table[:name].lower.matches(pattern))
query = query.where(arel_table[:name].lower.eq(normalized_term).or(arel_table[:reviewed_at].not_eq(nil))) if options[:exclude_unreviewed]
Tag.listable
.where(arel_table[:name].lower.matches(pattern))
.where(arel_table[:name].lower.eq(normalized_term).or(arel_table[:reviewed_at].not_eq(nil)))
.order(Arel.sql('length(name) ASC, name ASC'))
.limit(limit)
.offset(offset)
query.order(Arel.sql('length(name) ASC, name ASC'))
.limit(limit)
.offset(offset)
end
def find_normalized(name)

View File

@ -169,7 +169,7 @@ class User < ApplicationRecord
end
def functional?
confirmed? && approved? && !disabled? && !account.suspended?
confirmed? && approved? && !disabled? && !account.suspended? && account.moved_to_account_id.nil?
end
def unconfirmed_or_pending?
@ -265,17 +265,20 @@ class User < ApplicationRecord
end
def password_required?
return false if Devise.pam_authentication || Devise.ldap_authentication
return false if external?
super
end
def send_reset_password_instructions
return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
return false if encrypted_password.blank?
super
end
def reset_password!(new_password, new_password_confirmation)
return false if encrypted_password.blank? && (Devise.pam_authentication || Devise.ldap_authentication)
return false if encrypted_password.blank?
super
end

View File

@ -20,6 +20,10 @@ class Web::PushSubscription < ApplicationRecord
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription
validates :endpoint, presence: true
validates :key_p256dh, presence: true
validates :key_auth, presence: true
def push(notification)
I18n.with_locale(associated_user&.locale || I18n.default_locale) do
push_payload(payload_for_notification(notification), 48.hours.seconds)