about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.babelrc1
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--app/javascript/mastodon/actions/compose.js12
-rw-r--r--app/lib/request.rb13
-rw-r--r--app/models/account.rb6
-rw-r--r--app/services/activitypub/process_account_service.rb2
-rw-r--r--app/validators/unique_username_validator.rb14
-rw-r--r--config/application.rb4
-rw-r--r--config/environments/production.rb4
-rw-r--r--config/initializers/devise.rb3
-rw-r--r--lib/devise/ldap_authenticatable.rb76
-rw-r--r--lib/mastodon/version.rb2
-rw-r--r--spec/lib/request_spec.rb11
14 files changed, 105 insertions, 57 deletions
diff --git a/.babelrc b/.babelrc
index ed28aa500..190b5038c 100644
--- a/.babelrc
+++ b/.babelrc
@@ -4,7 +4,6 @@
     [
       "env",
       {
-        "debug": true,
         "exclude": ["transform-async-to-generator", "transform-regenerator"],
         "loose": true,
         "modules": false,
diff --git a/Gemfile b/Gemfile
index 195ff6e14..47d941994 100644
--- a/Gemfile
+++ b/Gemfile
@@ -73,7 +73,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
 gem 'rqrcode', '~> 0.10'
 gem 'ruby-oembed', '~> 0.12', require: 'oembed'
 gem 'ruby-progressbar', '~> 1.4'
-gem 'sanitize', '~> 4.4'
+gem 'sanitize', '~> 4.6.4'
 gem 'sidekiq', '~> 5.0'
 gem 'sidekiq-scheduler', '~> 2.1'
 gem 'sidekiq-unique-jobs', '~> 5.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4ae407f79..f30cd518e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -290,7 +290,7 @@ GEM
       activesupport (>= 4, < 5.2)
       railties (>= 4, < 5.2)
       request_store (~> 1.0)
-    loofah (2.1.1)
+    loofah (2.2.1)
       crass (~> 1.0.2)
       nokogiri (>= 1.5.9)
     mail (2.7.0)
@@ -318,9 +318,9 @@ GEM
       net-ssh (>= 2.6.5)
     net-ssh (4.2.0)
     nio4r (2.1.0)
-    nokogiri (1.8.1)
+    nokogiri (1.8.2)
       mini_portile2 (~> 2.3.0)
-    nokogumbo (1.4.13)
+    nokogumbo (1.5.0)
       nokogiri
     nsa (0.2.4)
       activesupport (>= 4.2, < 6)
@@ -499,10 +499,10 @@ GEM
     rufus-scheduler (3.4.2)
       et-orbi (~> 1.0)
     safe_yaml (1.0.4)
-    sanitize (4.5.0)
+    sanitize (4.6.4)
       crass (~> 1.0.2)
       nokogiri (>= 1.4.4)
-      nokogumbo (~> 1.4.1)
+      nokogumbo (~> 1.4)
     sass (3.5.3)
       sass-listen (~> 4.0.0)
     sass-listen (4.0.0)
@@ -704,7 +704,7 @@ DEPENDENCIES
   rubocop
   ruby-oembed (~> 0.12)
   ruby-progressbar (~> 1.4)
-  sanitize (~> 4.4)
+  sanitize (~> 4.6.4)
   scss_lint (~> 0.55)
   sidekiq (~> 5.0)
   sidekiq-bulk (~> 0.1.1)
diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js
index 130b4af23..1371f22b2 100644
--- a/app/javascript/mastodon/actions/compose.js
+++ b/app/javascript/mastodon/actions/compose.js
@@ -1,4 +1,5 @@
 import api from '../api';
+import { CancelToken } from 'axios';
 import { throttle } from 'lodash';
 import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
 import { tagHistory } from '../settings';
@@ -11,6 +12,8 @@ import {
   refreshPublicTimeline,
 } from './timelines';
 
+let cancelFetchComposeSuggestionsAccounts;
+
 export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE';
 export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST';
 export const COMPOSE_SUBMIT_SUCCESS  = 'COMPOSE_SUBMIT_SUCCESS';
@@ -257,13 +260,22 @@ export function undoUploadCompose(media_id) {
 };
 
 export function clearComposeSuggestions() {
+  if (cancelFetchComposeSuggestionsAccounts) {
+    cancelFetchComposeSuggestionsAccounts();
+  }
   return {
     type: COMPOSE_SUGGESTIONS_CLEAR,
   };
 };
 
 const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
+  if (cancelFetchComposeSuggestionsAccounts) {
+    cancelFetchComposeSuggestionsAccounts();
+  }
   api(getState).get('/api/v1/accounts/search', {
+    cancelToken: new CancelToken(cancel => {
+      cancelFetchComposeSuggestionsAccounts = cancel;
+    }),
     params: {
       q: token.slice(1),
       resolve: false,
diff --git a/app/lib/request.rb b/app/lib/request.rb
index 5776b3d78..298fb9528 100644
--- a/app/lib/request.rb
+++ b/app/lib/request.rb
@@ -94,9 +94,16 @@ class Request
   class Socket < TCPSocket
     class << self
       def open(host, *args)
-        address = IPSocket.getaddress(host)
-        raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address)
-        super address, *args
+        outer_e = nil
+        Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address|
+          begin
+            raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address.ip_address)
+            return super address.ip_address, *args
+          rescue => e
+            outer_e = e
+          end
+        end
+        raise outer_e if outer_e
       end
 
       alias new open
diff --git a/app/models/account.rb b/app/models/account.rb
index 61f81ab70..16a256bfc 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -47,7 +47,8 @@
 #
 
 class Account < ApplicationRecord
-  MENTION_RE = /(?<=^|[^\/[:word:]])@(([a-z0-9_]+)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
+  USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.]+[a-z0-9_]+)?/i
+  MENTION_RE  = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE}?)(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
 
   include AccountAvatar
   include AccountFinderConcern
@@ -70,7 +71,8 @@ class Account < ApplicationRecord
   validates :username, uniqueness: { scope: :domain, case_sensitive: true }, if: -> { !local? && will_save_change_to_username? }
 
   # Local user validations
-  validates :username, format: { with: /\A[a-z0-9_]+\z/i }, uniqueness: { scope: :domain, case_sensitive: false }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
+  validates :username, format: { with: /\A#{USERNAME_RE}\z/i }, length: { maximum: 30 }, if: -> { local? && will_save_change_to_username? }
+  validates_with UniqueUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? }
   validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
   validate :note_length_does_not_exceed_length_limit, if: -> { local? && will_save_change_to_note? }
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 68e9db766..7d8dc1369 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -16,7 +16,7 @@ class ActivityPub::ProcessAccountService < BaseService
 
     RedisLock.acquire(lock_options) do |lock|
       if lock.acquired?
-        @account        = Account.find_by(uri: @uri)
+        @account        = Account.find_remote(@username, @domain)
         @old_public_key = @account&.public_key
         @old_protocol   = @account&.protocol
 
diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb
new file mode 100644
index 000000000..c76407b16
--- /dev/null
+++ b/app/validators/unique_username_validator.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class UniqueUsernameValidator < ActiveModel::Validator
+  def validate(account)
+    return if account.username.nil?
+
+    normalized_username = account.username.downcase.delete('.')
+
+    scope = Account.where(domain: nil, username: normalized_username)
+    scope = scope.where.not(id: account.id) if account.persisted?
+
+    account.errors.add(:username, :taken) if scope.exists?
+  end
+end
diff --git a/config/application.rb b/config/application.rb
index 4319167dc..c0899ad70 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -77,9 +77,7 @@ module Mastodon
     ]
 
     config.i18n.default_locale = ENV['DEFAULT_LOCALE']&.to_sym
-    if config.i18n.available_locales.include?(config.i18n.default_locale)
-      config.i18n.fallbacks = [:en]
-    else
+    unless config.i18n.available_locales.include?(config.i18n.default_locale)
       config.i18n.default_locale = :en
     end
 
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 6cd13d441..7a800db19 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -55,8 +55,8 @@ Rails.application.configure do
   # config.action_mailer.raise_delivery_errors = false
 
   # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
-  # the I18n.default_locale when a translation cannot be found).
-  config.i18n.fallbacks = true
+  # English when a translation cannot be found).
+  config.i18n.fallbacks = [:en]
 
   # Send deprecation notices to registered listeners.
   config.active_support.deprecation = :notify
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 97757d0fb..e0d263f16 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -55,6 +55,8 @@ module Devise
   @@ldap_bind_dn = nil
   mattr_accessor :ldap_password
   @@ldap_password = nil
+  mattr_accessor :ldap_tls_no_verify
+  @@ldap_tls_no_verify = false
 
   class Strategies::PamAuthenticatable
     def valid?
@@ -357,5 +359,6 @@ Devise.setup do |config|
     config.ldap_bind_dn        = ENV.fetch('LDAP_BIND_DN')
     config.ldap_password       = ENV.fetch('LDAP_PASSWORD')
     config.ldap_uid            = ENV.fetch('LDAP_UID', 'cn')
+    config.ldap_tls_no_verify  = ENV['LDAP_TLS_NO_VERIFY'] == 'true'
   end
 end
diff --git a/lib/devise/ldap_authenticatable.rb b/lib/devise/ldap_authenticatable.rb
index 531abdbbe..ef786fbb7 100644
--- a/lib/devise/ldap_authenticatable.rb
+++ b/lib/devise/ldap_authenticatable.rb
@@ -1,49 +1,53 @@
 # frozen_string_literal: true
 
-if ENV['LDAP_ENABLED'] == 'true'
-  require 'net/ldap'
-  require 'devise/strategies/authenticatable'
+require 'net/ldap'
+require 'devise/strategies/authenticatable'
 
-  module Devise
-    module Strategies
-      class LdapAuthenticatable < Authenticatable
-        def authenticate!
-          if params[:user]
-            ldap = Net::LDAP.new(
-              host: Devise.ldap_host,
-              port: Devise.ldap_port,
-              base: Devise.ldap_base,
-              encryption: {
-                method: Devise.ldap_method,
-                tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS,
-              },
-              auth: {
-                method: :simple,
-                username: Devise.ldap_bind_dn,
-                password: Devise.ldap_password,
-              },
-              connect_timeout: 10
-            )
+module Devise
+  module Strategies
+    class LdapAuthenticatable < Authenticatable
+      def authenticate!
+        if params[:user]
+          ldap = Net::LDAP.new(
+            host: Devise.ldap_host,
+            port: Devise.ldap_port,
+            base: Devise.ldap_base,
+            encryption: {
+              method: Devise.ldap_method,
+              tls_options: tls_options,
+            },
+            auth: {
+              method: :simple,
+              username: Devise.ldap_bind_dn,
+              password: Devise.ldap_password,
+            },
+            connect_timeout: 10
+          )
 
-            if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: "(#{Devise.ldap_uid}=#{email})", password: password))
-              user = User.ldap_get_user(user_info.first)
-              success!(user)
-            else
-              return fail(:invalid_login)
-            end
+          if (user_info = ldap.bind_as(base: Devise.ldap_base, filter: "(#{Devise.ldap_uid}=#{email})", password: password))
+            user = User.ldap_get_user(user_info.first)
+            success!(user)
+          else
+            return fail(:invalid_login)
           end
         end
+      end
 
-        def email
-          params[:user][:email]
-        end
+      def email
+        params[:user][:email]
+      end
 
-        def password
-          params[:user][:password]
+      def password
+        params[:user][:password]
+      end
+
+      def tls_options
+        OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.tap do |options|
+          options[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if Devise.ldap_tls_no_verify
         end
       end
     end
   end
-
-  Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
 end
+
+Warden::Strategies.add(:ldap_authenticatable, Devise::Strategies::LdapAuthenticatable)
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 78a2dd901..80650761a 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -21,7 +21,7 @@ module Mastodon
     end
 
     def flags
-      'rc3'
+      'rc4'
     end
 
     def to_a
diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb
index dc7daa52c..5da357c55 100644
--- a/spec/lib/request_spec.rb
+++ b/spec/lib/request_spec.rb
@@ -48,6 +48,13 @@ describe Request do
         expect(a_request(:get, 'http://example.com')).to have_been_made.once
       end
 
+      it 'executes a HTTP request when the first address is private' do
+        allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM)
+                                            .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM))
+                                            .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:4860:4860::8844"], :PF_INET6, :SOCK_STREAM))
+        expect(a_request(:get, 'http://example.com')).to have_been_made.once
+      end
+
       it 'sets headers' do
         expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
       end
@@ -61,7 +68,9 @@ describe Request do
       end
 
       it 'raises Mastodon::ValidationError' do
-        allow(IPSocket).to receive(:getaddress).with('example.com').and_return('0.0.0.0')
+        allow(Addrinfo).to receive(:foreach).with('example.com', nil, nil, :SOCK_STREAM)
+                                            .and_yield(Addrinfo.new(["AF_INET", 0, "example.com", "0.0.0.0"], :PF_INET, :SOCK_STREAM))
+                                            .and_yield(Addrinfo.new(["AF_INET6", 0, "example.com", "2001:db8::face"], :PF_INET6, :SOCK_STREAM))
         expect{ subject.perform }.to raise_error Mastodon::ValidationError
       end
     end