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

Conflicts:
- `app/controllers/activitypub/collections_controller.rb`:
  Conflict due to glitch-soc having to take care of local-only
  pinned toots in that controller.
  Took upstream's changes and restored the local-only special
  handling.
- `app/controllers/auth/sessions_controller.rb`:
  Minor conflicts due to the theming system, applied upstream
  changes, adapted the following two files for glitch-soc's
  theming system:
  - `app/controllers/concerns/sign_in_token_authentication_concern.rb`
  - `app/controllers/concerns/two_factor_authentication_concern.rb`
- `app/services/backup_service.rb`:
  Minor conflict due to glitch-soc having to handle local-only
  toots specially. Applied upstream changes and restored
  the local-only special handling.
- `app/views/admin/custom_emojis/index.html.haml`:
  Minor conflict due to the theming system.
- `package.json`:
  Upstream dependency updated, too close to a glitch-soc-only
  dependency in the file.
- `yarn.lock`:
  Upstream dependency updated, too close to a glitch-soc-only
  dependency in the file.
This commit is contained in:
Thibaut Girka
2020-06-09 10:39:20 +02:00
246 changed files with 5027 additions and 1354 deletions

View File

@@ -4,6 +4,7 @@ require 'rails_helper'
RSpec.describe ActivityPub::RepliesController, type: :controller do
let(:status) { Fabricate(:status, visibility: parent_visibility) }
let(:remote_reply_id) { nil }
let(:remote_account) { nil }
before do
@@ -14,6 +15,8 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
Fabricate(:status, thread: status, visibility: :private)
Fabricate(:status, account: status.account, thread: status, visibility: :public)
Fabricate(:status, account: status.account, thread: status, visibility: :private)
Fabricate(:status, account: remote_account, thread: status, visibility: :public, uri: remote_reply_id) if remote_reply_id
end
describe 'GET #index' do
@@ -110,6 +113,20 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
expect(json[:first][:items].size).to eq 2
expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
context 'with remote responses' do
let(:remote_reply_id) { 'foo' }
it 'returned items are all inlined local toots or are ids' do
json = body_as_json
expect(json[:first]).to be_a Hash
expect(json[:first][:items]).to be_an Array
expect(json[:first][:items].size).to eq 3
expect(json[:first][:items].all? { |item| item.is_a?(Hash) ? ActivityPub::TagManager.instance.local_uri?(item[:id]) : item.is_a?(String) }).to be true
expect(json[:first][:items]).to include remote_reply_id
end
end
end
end

View File

@@ -215,7 +215,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using a valid OTP' do
before do
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { otp_user_id: user.id }
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id }
end
it 'redirects to home' do
@@ -230,7 +230,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'when the server has an decryption error' do
before do
allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_raise(OpenSSL::Cipher::CipherError)
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { otp_user_id: user.id }
post :create, params: { user: { otp_attempt: user.current_otp } }, session: { attempt_user_id: user.id }
end
it 'shows a login error' do
@@ -244,7 +244,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using a valid recovery code' do
before do
post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { otp_user_id: user.id }
post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { attempt_user_id: user.id }
end
it 'redirects to home' do
@@ -258,7 +258,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
context 'using an invalid OTP' do
before do
post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { otp_user_id: user.id }
post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id }
end
it 'shows a login error' do
@@ -270,5 +270,63 @@ RSpec.describe Auth::SessionsController, type: :controller do
end
end
end
context 'when 2FA is disabled and IP is unfamiliar' do
let!(:user) { Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', current_sign_in_at: 3.weeks.ago, current_sign_in_ip: '0.0.0.0') }
before do
request.remote_ip = '10.10.10.10'
request.user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0'
allow(UserMailer).to receive(:sign_in_token).and_return(double('email', deliver_later!: nil))
end
context 'using email and password' do
before do
post :create, params: { user: { email: user.email, password: user.password } }
end
it 'renders sign in token authentication page' do
expect(controller).to render_template("sign_in_token")
end
it 'generates sign in token' do
expect(user.reload.sign_in_token).to_not be_nil
end
it 'sends sign in token e-mail' do
expect(UserMailer).to have_received(:sign_in_token)
end
end
context 'using a valid sign in token' do
before do
user.generate_sign_in_token && user.save
post :create, params: { user: { sign_in_token_attempt: user.sign_in_token } }, session: { attempt_user_id: user.id }
end
it 'redirects to home' do
expect(response).to redirect_to(root_path)
end
it 'logs the user in' do
expect(controller.current_user).to eq user
end
end
context 'using an invalid sign in token' do
before do
post :create, params: { user: { sign_in_token_attempt: 'wrongotp' } }, session: { attempt_user_id: user.id }
end
it 'shows a login error' do
expect(flash[:alert]).to match I18n.t('users.invalid_sign_in_token')
end
it "doesn't log the user in" do
expect(controller.current_user).to be_nil
end
end
end
end
end

View File

@@ -0,0 +1,8 @@
Fabricator(:device) do
access_token
account
device_id { Faker::Number.number(digits: 5) }
name { Faker::App.name }
fingerprint_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) }
identity_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) }
end

View File

@@ -0,0 +1,8 @@
Fabricator(:encrypted_message) do
device
from_account
from_device_id { Faker::Number.number(digits: 5) }
type 0
body ""
message_franking ""
end

View File

@@ -0,0 +1,11 @@
Fabricator(:one_time_key) do
device
key_id { Faker::Alphanumeric.alphanumeric(number: 10) }
key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) }
signature do |attrs|
signing_key = Ed25519::SigningKey.generate
attrs[:device].update(fingerprint_key: Base64.strict_encode64(signing_key.verify_key.to_bytes))
Base64.strict_encode64(signing_key.sign(attrs[:key]))
end
end

View File

@@ -0,0 +1,3 @@
Fabricator(:system_key) do
end

View File

@@ -579,6 +579,62 @@ RSpec.describe ActivityPub::Activity::Create do
end
end
context 'with an encrypted message' do
let(:recipient) { Fabricate(:account) }
let(:target_device) { Fabricate(:device, account: recipient) }
subject { described_class.new(json, sender, delivery: true, delivered_to_account_id: recipient.id) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'EncryptedMessage',
attributedTo: {
type: 'Device',
deviceId: '1234',
},
to: {
type: 'Device',
deviceId: target_device.device_id,
},
messageType: 1,
cipherText: 'Foo',
messageFranking: 'Baz678',
digest: {
digestAlgorithm: 'Bar456',
digestValue: 'Foo123',
},
}
end
before do
subject.perform
end
it 'creates an encrypted message' do
encrypted_message = target_device.encrypted_messages.reload.first
expect(encrypted_message).to_not be_nil
expect(encrypted_message.from_device_id).to eq '1234'
expect(encrypted_message.from_account).to eq sender
expect(encrypted_message.type).to eq 1
expect(encrypted_message.body).to eq 'Foo'
expect(encrypted_message.digest).to eq 'Foo123'
end
it 'creates a message franking' do
encrypted_message = target_device.encrypted_messages.reload.first
message_franking = encrypted_message.message_franking
crypt = ActiveSupport::MessageEncryptor.new(SystemKey.current_key, serializer: Oj)
json = crypt.decrypt_and_verify(message_franking)
expect(json['source_account_id']).to eq sender.id
expect(json['target_account_id']).to eq recipient.id
expect(json['original_franking']).to eq 'Baz678'
end
end
context 'when sender is followed by local users' do
subject { described_class.new(json, sender, delivery: true) }

View File

@@ -59,4 +59,9 @@ class UserMailerPreview < ActionMailer::Preview
def warning
UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id])
end
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token
def sign_in_token
UserMailer.sign_in_token(User.first.tap { |user| user.generate_sign_in_token }, '127.0.0.1', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0', Time.now.utc)
end
end

View File

@@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Device, type: :model do
end

View File

@@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe EncryptedMessage, type: :model do
end

View File

@@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe OneTimeKey, type: :model do
end

View File

@@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe SystemKey, type: :model do
end

View File

@@ -0,0 +1,23 @@
require 'rails_helper'
RSpec.describe ClearDomainMediaService, type: :service do
let!(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') }
let!(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') }
let!(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') }
let!(:bad_attachment) { Fabricate(:media_attachment, account: bad_account, status: bad_status2, file: attachment_fixture('attachment.jpg')) }
subject { ClearDomainMediaService.new }
describe 'for a silence with reject media' do
before do
subject.call(DomainBlock.create!(domain: 'evil.org', severity: :silence, reject_media: true))
end
it 'leaves the domains status and attachements, but clears media' do
expect { bad_status1.reload }.not_to raise_error
expect { bad_status2.reload }.not_to raise_error
expect { bad_attachment.reload }.not_to raise_error
expect(bad_attachment.file.exists?).to be false
end
end
end

View File

@@ -8,7 +8,7 @@ describe DomainBlockWorker do
describe 'perform' do
let(:domain_block) { Fabricate(:domain_block) }
it 'returns true for non-existent domain block' do
it 'calls domain block service for relevant domain block' do
service = double(call: nil)
allow(BlockDomainService).to receive(:new).and_return(service)
result = subject.perform(domain_block.id)
@@ -17,7 +17,7 @@ describe DomainBlockWorker do
expect(service).to have_received(:call).with(domain_block, false)
end
it 'calls domain block service for relevant domain block' do
it 'returns true for non-existent domain block' do
result = subject.perform('aaa')
expect(result).to eq(true)

View File

@@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'rails_helper'
describe DomainClearMediaWorker do
subject { described_class.new }
describe 'perform' do
let(:domain_block) { Fabricate(:domain_block, severity: :silence, reject_media: true) }
it 'calls domain clear media service for relevant domain block' do
service = double(call: nil)
allow(ClearDomainMediaService).to receive(:new).and_return(service)
result = subject.perform(domain_block.id)
expect(result).to be_nil
expect(service).to have_received(:call).with(domain_block)
end
it 'returns true for non-existent domain block' do
result = subject.perform('aaa')
expect(result).to eq(true)
end
end
end