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

Conflicts:
- `app/controllers/concerns/sign_in_token_authentication_concern.rb`:
  Upstream removed this file, while glitch-soc had changes to deal with
  its theming system.
  Removed the file like upstream did.
This commit is contained in:
Claire
2022-04-06 21:10:23 +02:00
34 changed files with 405 additions and 406 deletions

View File

@ -225,22 +225,6 @@ RSpec.describe Auth::SessionsController, type: :controller do
end
end
context 'using email and password after an unfinished log-in attempt with a sign-in token challenge' do
let!(:other_user) do
Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
end
before do
post :create, params: { user: { email: other_user.email, password: other_user.password } }
post :create, params: { user: { email: user.email, password: user.password } }
end
it 'renders two factor authentication page' do
expect(controller).to render_template("two_factor")
expect(controller).to render_template(partial: "_otp_authentication_form")
end
end
context 'using upcase email and password' do
before do
post :create, params: { user: { email: user.email.upcase, password: user.password } }
@ -266,21 +250,6 @@ RSpec.describe Auth::SessionsController, type: :controller do
end
end
context 'using a valid OTP, attempting to leverage previous half-login to bypass password auth' do
let!(:other_user) do
Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
end
before do
post :create, params: { user: { email: other_user.email, password: other_user.password } }
post :create, params: { user: { email: user.email, otp_attempt: user.current_otp } }, session: { attempt_user_updated_at: user.updated_at.to_s }
end
it "doesn't log the user in" do
expect(controller.current_user).to be_nil
end
end
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)
@ -401,126 +370,6 @@ 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) }
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 email and password after an unfinished log-in attempt to a 2FA-protected account' do
let!(:other_user) do
Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
end
before do
post :create, params: { user: { email: other_user.email, password: other_user.password } }
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 email and password after an unfinished log-in attempt with a sign-in token challenge' do
let!(:other_user) do
Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
end
before do
post :create, params: { user: { email: other_user.email, password: other_user.password } }
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).with(user, any_args)
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, attempt_user_updated_at: user.updated_at.to_s }
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 a valid sign in token, attempting to leverage previous half-login to bypass password auth' do
let!(:other_user) do
Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: false, current_sign_in_at: 1.month.ago)
end
before do
user.generate_sign_in_token && user.save
post :create, params: { user: { email: other_user.email, password: other_user.password } }
post :create, params: { user: { email: user.email, sign_in_token_attempt: user.sign_in_token } }, session: { attempt_user_updated_at: user.updated_at.to_s }
end
it "doesn't log the user in" do
expect(controller.current_user).to be_nil
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, attempt_user_updated_at: user.updated_at.to_s }
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
describe 'GET #webauthn_options' do

View File

@ -0,0 +1,57 @@
require 'rails_helper'
RSpec.describe SuspiciousSignInDetector do
describe '#suspicious?' do
let(:user) { Fabricate(:user, current_sign_in_at: 1.day.ago) }
let(:request) { double(remote_ip: remote_ip) }
let(:remote_ip) { nil }
subject { described_class.new(user).suspicious?(request) }
context 'when user has 2FA enabled' do
before do
user.update!(otp_required_for_login: true)
end
it 'returns false' do
expect(subject).to be false
end
end
context 'when exact IP has been used before' do
let(:remote_ip) { '1.1.1.1' }
before do
user.update!(sign_up_ip: remote_ip)
end
it 'returns false' do
expect(subject).to be false
end
end
context 'when similar IP has been used before' do
let(:remote_ip) { '1.1.2.2' }
before do
user.update!(sign_up_ip: '1.1.1.1')
end
it 'returns false' do
expect(subject).to be false
end
end
context 'when IP is completely unfamiliar' do
let(:remote_ip) { '2.2.2.2' }
before do
user.update!(sign_up_ip: '1.1.1.1')
end
it 'returns true' do
expect(subject).to be true
end
end
end
end

View File

@ -87,8 +87,8 @@ class UserMailerPreview < ActionMailer::Preview
UserMailer.appeal_approved(User.first, Appeal.last)
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)
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/suspicious_sign_in
def suspicious_sign_in
UserMailer.suspicious_sign_in(User.first, '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

@ -195,7 +195,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
let(:existing_status) { Fabricate(:status, account: sender, text: 'Foo', uri: note[:id]) }
context 'with a Note object' do
let(:object) { note }
let(:object) { note.merge(updated: '2021-09-08T22:39:25Z') }
it 'updates status' do
existing_status.reload
@ -211,7 +211,7 @@ RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do
id: "https://#{valid_domain}/@foo/1234/create",
type: 'Create',
actor: ActivityPub::TagManager.instance.uri_for(sender),
object: note,
object: note.merge(updated: '2021-09-08T22:39:25Z'),
}
end

View File

@ -1,5 +1,9 @@
require 'rails_helper'
def poll_option_json(name, votes)
{ type: 'Note', name: name, replies: { type: 'Collection', totalItems: votes } }
end
RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
let!(:status) { Fabricate(:status, text: 'Hello world', account: Fabricate(:account, domain: 'example.com')) }
@ -46,6 +50,180 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService, type: :service do
expect(status.reload.spoiler_text).to eq 'Show more'
end
context 'when the changes are only in sanitized-out HTML' do
let!(:status) { Fabricate(:status, text: '<p>Hello world <a href="https://joinmastodon.org" rel="nofollow">joinmastodon.org</a></p>', account: Fabricate(:account, domain: 'example.com')) }
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Note',
updated: '2021-09-08T22:39:25Z',
content: '<p>Hello world <a href="https://joinmastodon.org" rel="noreferrer">joinmastodon.org</a></p>',
}
end
before do
subject.call(status, json)
end
it 'does not create any edits' do
expect(status.reload.edits).to be_empty
end
it 'does not mark status as edited' do
expect(status.edited?).to be false
end
end
context 'when the status has not been explicitly edited' do
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'foo',
type: 'Note',
content: 'Updated text',
}
end
before do
subject.call(status, json)
end
it 'does not create any edits' do
expect(status.reload.edits).to be_empty
end
it 'does not mark status as edited' do
expect(status.reload.edited?).to be false
end
it 'does not update the text' do
expect(status.reload.text).to eq 'Hello world'
end
end
context 'when the status has not been explicitly edited and features a poll' do
let(:account) { Fabricate(:account, domain: 'example.com') }
let!(:expiration) { 10.days.from_now.utc }
let!(:status) do
Fabricate(:status,
text: 'Hello world',
account: account,
poll_attributes: {
options: %w(Foo Bar),
account: account,
multiple: false,
hide_totals: false,
expires_at: expiration
}
)
end
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'https://example.com/foo',
type: 'Question',
content: 'Hello world',
endTime: expiration.iso8601,
oneOf: [
poll_option_json('Foo', 4),
poll_option_json('Bar', 3),
],
}
end
before do
subject.call(status, json)
end
it 'does not create any edits' do
expect(status.reload.edits).to be_empty
end
it 'does not mark status as edited' do
expect(status.reload.edited?).to be false
end
it 'does not update the text' do
expect(status.reload.text).to eq 'Hello world'
end
it 'updates tallies' do
expect(status.poll.reload.cached_tallies).to eq [4, 3]
end
end
context 'when the status changes a poll despite being not explicitly marked as updated' do
let(:account) { Fabricate(:account, domain: 'example.com') }
let!(:expiration) { 10.days.from_now.utc }
let!(:status) do
Fabricate(:status,
text: 'Hello world',
account: account,
poll_attributes: {
options: %w(Foo Bar),
account: account,
multiple: false,
hide_totals: false,
expires_at: expiration
}
)
end
let(:payload) do
{
'@context': 'https://www.w3.org/ns/activitystreams',
id: 'https://example.com/foo',
type: 'Question',
content: 'Hello world',
endTime: expiration.iso8601,
oneOf: [
poll_option_json('Foo', 4),
poll_option_json('Bar', 3),
poll_option_json('Baz', 3),
],
}
end
before do
subject.call(status, json)
end
it 'does not create any edits' do
expect(status.reload.edits).to be_empty
end
it 'does not mark status as edited' do
expect(status.reload.edited?).to be false
end
it 'does not update the text' do
expect(status.reload.text).to eq 'Hello world'
end
it 'does not update tallies' do
expect(status.poll.reload.cached_tallies).to eq [0, 0]
end
end
context 'when receiving an edit older than the latest processed' do
before do
status.snapshot!(at_time: status.created_at, rate_limit: false)
status.update!(text: 'Hello newer world', edited_at: Time.now.utc)
status.snapshot!(rate_limit: false)
end
it 'does not create any edits' do
expect { subject.call(status, json) }.not_to change { status.reload.edits.pluck(&:id) }
end
it 'does not update the text, spoiler_text or edited_at' do
expect { subject.call(status, json) }.not_to change { s = status.reload; [s.text, s.spoiler_text, s.edited_at] }
end
end
context 'with no changes at all' do
let(:payload) do
{