Merge commit '5e1ec8b18a48b36b0fc66f04292df9c519b21883' into glitch-soc/stable-4.6

This commit is contained in:
Claire committed 2026-09-01 16:07:09 +02:00
commit 3a5da4cb92
52 files changed
+439 -166

No files matched your search

+16
View File
@@ -2,6 +2,22 @@
All notable changes to this project will be documented in this file.
## [4.6.7] - 2026-09-01
## Security
- Update dependencies
- Fix password authentication bypass in 2FA auth for LDAP/PAM/SSO accounts ([GHSA-vx32-x96w-qq65](https://github.com/mastodon/mastodon/security/advisories/GHSA-vx32-x96w-qq65))
- Fix Denial of Service when processing pathological JSON-LD activities ([GHSA-vgm8-frgh-rh2v](https://github.com/mastodon/mastodon/security/advisories/GHSA-vgm8-frgh-rh2v))
- Fix disabled staff accounts still having access to admin API ([GHSA-62j4-hvj7-px3f](https://github.com/mastodon/mastodon/security/advisories/GHSA-62j4-hvj7-px3f))
### Fixes
- Fix `config/` directory missing from Bootsnap precompilation options in Dockerfile (#40255 by @ClearlyClaire)
- Fix autosuggestions overstaying their welcome in languages that don't use spaces (#40217 by @Gargron)
- Fix error when processing remote actors with `null` public keys (#40194 by @ClearlyClaire)
- Fix various off-by-one errors in statistics time ranges (#40193 by @ClearlyClaire)
## [4.6.6] - 2026-08-13
### Changed
+54 -54
View File
@@ -12,29 +12,29 @@ GEM
specs:
action_text-trix (2.1.19)
railties
actioncable (8.1.3)
actionpack (= 8.1.3)
activesupport (= 8.1.3)
actioncable (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.3)
actionpack (= 8.1.3)
activejob (= 8.1.3)
activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
actionmailbox (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
mail (>= 2.8.0)
actionmailer (8.1.3)
actionpack (= 8.1.3)
actionview (= 8.1.3)
activejob (= 8.1.3)
activesupport (= 8.1.3)
actionmailer (8.1.3.1)
actionpack (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activesupport (= 8.1.3.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.3)
actionview (= 8.1.3)
activesupport (= 8.1.3)
actionpack (8.1.3.1)
actionview (= 8.1.3.1)
activesupport (= 8.1.3.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -42,16 +42,16 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.1.3)
actiontext (8.1.3.1)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.3)
activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
actionpack (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.3)
activesupport (= 8.1.3)
actionview (8.1.3.1)
activesupport (= 8.1.3.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@@ -61,22 +61,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (8.1.3)
activesupport (= 8.1.3)
activejob (8.1.3.1)
activesupport (= 8.1.3.1)
globalid (>= 0.3.6)
activemodel (8.1.3)
activesupport (= 8.1.3)
activerecord (8.1.3)
activemodel (= 8.1.3)
activesupport (= 8.1.3)
activemodel (8.1.3.1)
activesupport (= 8.1.3.1)
activerecord (8.1.3.1)
activemodel (= 8.1.3.1)
activesupport (= 8.1.3.1)
timeout (>= 0.4.0)
activestorage (8.1.3)
actionpack (= 8.1.3)
activejob (= 8.1.3)
activerecord (= 8.1.3)
activesupport (= 8.1.3)
activestorage (8.1.3.1)
actionpack (= 8.1.3.1)
activejob (= 8.1.3.1)
activerecord (= 8.1.3.1)
activesupport (= 8.1.3.1)
marcel (~> 1.0)
activesupport (8.1.3)
activesupport (8.1.3.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -431,7 +431,7 @@ GEM
loofah (2.25.2)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.0)
mail (2.9.1)
logger
mini_mime (>= 0.1.1)
net-imap
@@ -654,20 +654,20 @@ GEM
rack (>= 1.3)
rackup (2.3.1)
rack (>= 3)
rails (8.1.3)
actioncable (= 8.1.3)
actionmailbox (= 8.1.3)
actionmailer (= 8.1.3)
actionpack (= 8.1.3)
actiontext (= 8.1.3)
actionview (= 8.1.3)
activejob (= 8.1.3)
activemodel (= 8.1.3)
activerecord (= 8.1.3)
activestorage (= 8.1.3)
activesupport (= 8.1.3)
rails (8.1.3.1)
actioncable (= 8.1.3.1)
actionmailbox (= 8.1.3.1)
actionmailer (= 8.1.3.1)
actionpack (= 8.1.3.1)
actiontext (= 8.1.3.1)
actionview (= 8.1.3.1)
activejob (= 8.1.3.1)
activemodel (= 8.1.3.1)
activerecord (= 8.1.3.1)
activestorage (= 8.1.3.1)
activesupport (= 8.1.3.1)
bundler (>= 1.15.0)
railties (= 8.1.3)
railties (= 8.1.3.1)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
@@ -678,9 +678,9 @@ GEM
rails-i18n (8.1.0)
i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9)
railties (8.1.3)
actionpack (= 8.1.3)
activesupport (= 8.1.3)
railties (8.1.3.1)
actionpack (= 8.1.3.1)
activesupport (= 8.1.3.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
+7
View File
@@ -4,9 +4,11 @@ module Admin
class BaseController < ApplicationController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
layout 'admin'
before_action :require_moderator_or_admin_permissions
before_action :set_referrer_policy_header
after_action :verify_authorized
@@ -20,5 +22,10 @@ module Admin
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end
def require_moderator_or_admin_permissions
# not using #authorize here, so #verify_authorized still makes sure more fine-grained rules are enforced down the line
forbidden unless Admin::BasePolicy.new(current_account, :base).access?
end
end
end
@@ -2,6 +2,7 @@
class Api::V1::Admin::AccountActionsController < Api::BaseController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }
before_action :set_account
@@ -3,6 +3,7 @@
class Api::V1::Admin::AccountsController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
@@ -3,6 +3,7 @@
class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
@@ -2,6 +2,7 @@
class Api::V1::Admin::DimensionsController < Api::BaseController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }
before_action :set_dimensions
@@ -3,6 +3,7 @@
class Api::V1::Admin::DomainAllowsController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
MAX_LIMIT = 500
@@ -3,6 +3,7 @@
class Api::V1::Admin::DomainBlocksController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
MAX_LIMIT = 500
@@ -3,6 +3,7 @@
class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
@@ -3,6 +3,7 @@
class Api::V1::Admin::IpBlocksController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
@@ -2,6 +2,7 @@
class Api::V1::Admin::MeasuresController < Api::BaseController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }
before_action :set_measures
@@ -3,6 +3,7 @@
class Api::V1::Admin::ReportsController < Api::BaseController
include Authorization
include AccountableConcern
include Admin::PermissionsConcern
LIMIT = 100
@@ -2,6 +2,7 @@
class Api::V1::Admin::RetentionController < Api::BaseController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }
before_action :set_retention
@@ -2,6 +2,7 @@
class Api::V1::Admin::TagsController < Api::BaseController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }, only: [:index, :show]
before_action -> { authorize_if_got_token! :'admin:write' }, only: :update
@@ -2,6 +2,7 @@
class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController
include Authorization
include Admin::PermissionsConcern
LIMIT = 100
@@ -2,6 +2,7 @@
class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
@@ -2,6 +2,7 @@
class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
@@ -2,6 +2,7 @@
class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
include Authorization
include Admin::PermissionsConcern
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
+8 -2
View File
@@ -51,8 +51,14 @@ class Auth::SessionsController < Devise::SessionsController
def find_user_from_params
user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
user ||= User.find_for_authentication(email: user_params[:email])
user
if user.present?
@password_verified_externally = true
return user
end
user = User.find_for_authentication(email: user_params[:email])
user if user&.encrypted_password.present?
end
def user_params
@@ -0,0 +1,16 @@
# frozen_string_literal: true
module Admin::PermissionsConcern
extend ActiveSupport::Concern
included do
before_action :require_moderator_or_admin_permissions
end
private
def require_moderator_or_admin_permissions
# not using #authorize here, so #verify_authorized still makes sure more fine-grained rules are enforced down the line
forbidden unless Admin::BasePolicy.new(current_account, :base).access?
end
end
@@ -38,7 +38,7 @@ module Auth::TwoFactorAuthenticationConcern
def authenticate_with_two_factor
if user_params[:email].present?
user = self.resource = find_user_from_params
prompt_for_two_factor(user) if user&.external_or_valid_password?(user_params[:password])
prompt_for_two_factor(user) if user && (@password_verified_externally || user.valid_password?(user_params[:password]))
elsif session[:attempt_user_id]
user = self.resource = User.find_by(id: session[:attempt_user_id])
return if user.nil?
+24
View File
@@ -112,6 +112,30 @@ module JsonLdHelper
compacted
end
# Checks for unsupported JSON-LD features or too many nodes
def check_jsonld_limits!(json, budget = 1_000)
budget -= 1
if json.is_a?(Hash)
json.each do |key, value|
raise Mastodon::InvalidJsonLdError, 'contains unsupported JSON-LD features' if UNSUPPORTED_JSONLD_KEYWORDS.include?(key)
raise Mastodon::InvalidJsonLdError, 'has too many nodes' if budget < 1
budget = check_jsonld_limits!(value, budget)
end
elsif json.is_a?(Array)
json.each do |value|
raise Mastodon::InvalidJsonLdError, 'has too many nodes' if budget < 1
budget = check_jsonld_limits!(value, budget)
end
end
raise Mastodon::InvalidJsonLdError, 'has too many nodes' if budget < 0 # rubocop:disable Style/NumericPredicate
budget
end
def unsupported_jsonld_features?(json)
if json.is_a?(Hash)
json.any? { |key, value| UNSUPPORTED_JSONLD_KEYWORDS.include?(key) || unsupported_jsonld_features?(value) }
+1 -8
View File
@@ -317,13 +317,6 @@ class User < ApplicationRecord
super
end
def external_or_valid_password?(compare_password)
# If encrypted_password is blank, we got the user from LDAP or PAM,
# so credentials are already valid
encrypted_password.blank? || valid_password?(compare_password)
end
def send_reset_password_instructions
return false if encrypted_password.blank?
@@ -525,7 +518,7 @@ class User < ApplicationRecord
end
def invite_text_required?
Setting.require_invite_text && !open_registrations? && !invited? && !external? && !bypass_registration_checks?
Setting.require_invite_text && !open_registrations? && !invite&.bypass_approval? && !external? && !bypass_registration_checks?
end
def trigger_webhooks
+7
View File
@@ -0,0 +1,7 @@
# frozen_string_literal: true
class Admin::BasePolicy < ApplicationPolicy
def access?
role.administrator? || role.can?(*UserRole::Flags::CATEGORIES.fetch_values(:moderation, :administration, :devops).flatten)
end
end
+2
View File
@@ -19,6 +19,8 @@ class ApplicationPolicy
end
def role
return UserRole.nobody if current_user&.disabled?
current_user&.role || UserRole.nobody
end
end
@@ -13,10 +13,10 @@ class ActivityPub::ProcessCollectionService < BaseService
begin
@json = compact(@json) if @json['signature'].is_a?(Hash)
if unsupported_jsonld_features?(@json)
Rails.logger.debug { "JSON-LD document for #{value_or_id(@json['actor'])} contains unsupported JSON-LD features" }
@json = original_json.without('signature')
end
check_jsonld_limits!(@json)
rescue Mastodon::InvalidJsonLdError => e
Rails.logger.debug { "JSON-LD handling for document from #{value_or_id(@json['actor'])} skipped: #{e.message}" }
@json = original_json.without('signature')
rescue JSON::LD::JsonLdError => e
Rails.logger.debug { "Error when compacting JSON-LD document for #{value_or_id(@json['actor'])}: #{e.message}" }
@json = original_json.without('signature')
+2 -2
View File
@@ -60,7 +60,7 @@
required: true,
wrapper: :with_block_label
- if approved_registrations? && @invite.blank?
- if approved_registrations? && !@invite&.bypass_approval?
.fields-group
= f.simple_fields_for :invite_request, resource.invite_request || resource.build_invite_request do |invite_request_fields|
= invite_request_fields.input :text,
@@ -81,4 +81,4 @@
wrapper: :with_label
.actions
= f.button :button, @invite.present? ? t('auth.register') : sign_up_message, type: :submit
= f.button :button, @invite&.bypass_approval? ? t('auth.register') : sign_up_message, type: :submit
+3 -3
View File
@@ -60,7 +60,7 @@ services:
web:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/glitch-soc/mastodon:v4.6.6
image: ghcr.io/glitch-soc/mastodon:v4.6.7
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@@ -84,7 +84,7 @@ services:
# build:
# dockerfile: ./streaming/Dockerfile
# context: .
image: ghcr.io/glitch-soc/mastodon-streaming:v4.6.6
image: ghcr.io/glitch-soc/mastodon-streaming:v4.6.7
restart: always
env_file: .env.production
command: node ./streaming/index.js
@@ -103,7 +103,7 @@ services:
sidekiq:
# You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/glitch-soc/mastodon:v4.6.6
image: ghcr.io/glitch-soc/mastodon:v4.6.7
restart: always
env_file: .env.production
command: bundle exec sidekiq
+1
View File
@@ -15,6 +15,7 @@ module Mastodon
class SignatureVerificationError < Error; end
class MalformedHeaderError < Error; end
class RecursionLimitExceededError < Error; end
class InvalidJsonLdError < Error; end
class UnexpectedResponseError < Error
attr_reader :response
+1 -1
View File
@@ -13,7 +13,7 @@ module Mastodon
end
def patch
6
7
end
def default_prerelease
@@ -252,94 +252,6 @@ RSpec.describe Auth::RegistrationsController do
end
end
context 'with Approval-based registrations without invite' do
subject do
Setting.registrations_mode = 'approved'
request.headers['Accept-Language'] = accept_language
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', agreement: 'true' } }
end
it 'redirects to setup and creates user' do
subject
expect(response)
.to redirect_to auth_setup_path
expect(User.find_by(email: 'test@example.com'))
.to be_present
.and have_attributes(
locale: eq(accept_language),
approved: be(false)
)
end
end
context 'with Approval-based registrations with expired invite' do
subject do
Setting.registrations_mode = 'approved'
request.headers['Accept-Language'] = accept_language
invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.ago)
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', invite_code: invite.code, agreement: 'true' } }
end
it 'redirects to setup and creates user' do
subject
expect(response).to redirect_to auth_setup_path
expect(User.find_by(email: 'test@example.com'))
.to be_present
.and have_attributes(
locale: eq(accept_language),
approved: be(false)
)
end
end
context 'with Approval-based registrations with valid invite and required invite text' do
subject do
Setting.registrations_mode = 'approved'
Setting.require_invite_text = true
request.headers['Accept-Language'] = accept_language
invite = Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now)
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', invite_code: invite.code, agreement: 'true' } }
end
let!(:inviter) { Fabricate(:user, confirmed_at: 2.days.ago) }
it 'redirects to setup and creates user in a non-approved state' do
subject
expect(response).to redirect_to auth_setup_path
expect(User.find_by(email: 'test@example.com'))
.to be_present
.and have_attributes(
locale: eq(accept_language),
approved: be(false)
)
end
context 'when the inviting user has the permission to bypass approval' do
before do
inviter.role.update!(permissions: inviter.role.permissions | UserRole::FLAGS[:invite_bypass_approval])
end
it 'redirects to setup and creates user in an approved state' do
subject
expect(response).to redirect_to auth_setup_path
expect(User.find_by(email: 'test@example.com'))
.to be_present
.and have_attributes(
locale: eq(accept_language),
approved: be(true)
)
end
end
end
context 'with an already taken username' do
subject do
Setting.registrations_mode = 'open'
+1 -1
View File
@@ -374,7 +374,7 @@ RSpec.describe Mastodon::CLI::Accounts do
.to output_results(new_password)
expect(user).to have_received(:change_password!).with(new_password)
expect(user.reload).to_not be_external_or_valid_password(original_password)
expect(user.reload).to_not be_valid_password(original_password)
end
end
+1 -1
View File
@@ -462,7 +462,7 @@ RSpec.describe User do
.and remove_user_web_subscriptions
expect(user)
.to_not be_external_or_valid_password(original_password)
.to_not be_valid_password(original_password)
expect { session_activation.reload }
.to raise_error(ActiveRecord::RecordNotFound)
expect { web_push_subscription.reload }
+60
View File
@@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::BasePolicy do
let(:policy) { described_class }
let(:custom_devops_role) do
Fabricate(:user_role, permissions_as_keys: [:view_devops])
end
let(:custom_role_with_one_admin_permission) do
Fabricate(:user_role, permissions_as_keys: [:manage_announcements])
end
let(:custom_role_with_one_moderator_permission) do
Fabricate(:user_role, permissions_as_keys: [:manage_invites])
end
let(:admin) { Fabricate(:admin_user).account }
let(:moderator) { Fabricate(:moderator_user).account }
let(:devops_user) { Fabricate(:user, role: custom_devops_role).account }
let(:custom_admin) { Fabricate(:user, role: custom_role_with_one_admin_permission).account }
let(:custom_moderator) { Fabricate(:user, role: custom_role_with_one_moderator_permission).account }
let(:regular_user) { Fabricate(:account) }
permissions :access? do
context 'with an admin' do
it 'permits' do
expect(policy).to permit(admin, :base)
end
end
context 'with a moderator' do
it 'permits' do
expect(policy).to permit(moderator, :base)
end
end
context 'with a user with devops permissions' do
it 'permits' do
expect(policy).to permit(devops_user, :base)
end
end
context 'with a user with a custom role that grants a single admin permission' do
it 'permits' do
expect(policy).to permit(custom_admin, :base)
end
end
context 'with a user with a custom role that grants a single moderator permission' do
it 'permits' do
expect(policy).to permit(custom_moderator, :base)
end
end
context 'with a non-admin' do
it 'denies' do
expect(policy).to_not permit(regular_user, :base)
end
end
end
end
+37
View File
@@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ApplicationPolicy do
subject do
Class.new(described_class) do
def spec?
role.can?(:manage_users)
end
end
end
let(:admin) { Fabricate(:admin_user).account }
let(:john) { Fabricate(:account) }
let(:alice) { Fabricate(:admin_user, disabled: true).account }
permissions :spec? do
context 'when staff' do
it 'permits' do
expect(subject).to permit(admin)
end
end
context 'when not staff' do
it 'denies' do
expect(subject).to_not permit(john)
end
end
context 'when staff but disabled' do
it 'denies' do
expect(subject).to_not permit(alice)
end
end
end
end
@@ -51,6 +51,7 @@ RSpec.describe 'Account actions' do
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :disable, :user
@@ -67,6 +68,7 @@ RSpec.describe 'Account actions' do
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :sensitive, :account
@@ -83,6 +85,7 @@ RSpec.describe 'Account actions' do
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :silence, :account
@@ -99,6 +102,7 @@ RSpec.describe 'Account actions' do
it_behaves_like 'forbidden for wrong scope', 'admin:read admin:read:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it_behaves_like 'a successful notification delivery'
it_behaves_like 'a successful logged action', :suspend, :account
@@ -30,6 +30,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'read read:accounts admin:write admin:write:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
context 'when requesting active local staff accounts' do
let(:expected_results) { [admin_account] }
@@ -107,6 +108,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'read read:accounts admin:write admin:write:accounts'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns the requested account successfully', :aggregate_failures do
subject
@@ -144,6 +146,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'approves the user successfully', :aggregate_failures do
subject
@@ -202,6 +205,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'removes the user successfully and logs action', :aggregate_failures do
subject
@@ -255,6 +259,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'enables the user successfully', :aggregate_failures do
subject
@@ -290,6 +295,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'unsuspends the account successfully', :aggregate_failures do
subject
@@ -335,6 +341,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'unsensitizes the account successfully', :aggregate_failures do
subject
@@ -369,6 +376,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'unsilences the account successfully', :aggregate_failures do
subject
@@ -404,6 +412,7 @@ RSpec.describe 'Accounts' do
it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'deletes the account successfully', :aggregate_failures do
allow(Admin::AccountDeletionWorker).to receive(:perform_async)
@@ -15,6 +15,7 @@ RSpec.describe 'Canonical Email Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when there is no canonical email block' do
it 'returns an empty list' do
@@ -90,6 +91,7 @@ RSpec.describe 'Canonical Email Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when the requested canonical email block exists' do
it 'returns the requested canonical email block data correctly', :aggregate_failures do
@@ -127,6 +129,7 @@ RSpec.describe 'Canonical Email Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when the required email param is not provided' do
let(:params) { {} }
@@ -178,6 +181,7 @@ RSpec.describe 'Canonical Email Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the canonical_email_hash correctly', :aggregate_failures do
subject
@@ -252,6 +256,7 @@ RSpec.describe 'Canonical Email Blocks' do
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'deletes the canonical email block', :aggregate_failures do
subject
@@ -15,6 +15,7 @@ RSpec.describe 'Domain Allows' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when there is no allowed domains' do
it 'returns an empty body' do
@@ -73,6 +74,7 @@ RSpec.describe 'Domain Allows' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the expected allowed domain name', :aggregate_failures do
subject
@@ -104,6 +106,7 @@ RSpec.describe 'Domain Allows' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'with a valid domain name' do
it 'returns the expected domain name', :aggregate_failures do
@@ -164,6 +167,7 @@ RSpec.describe 'Domain Allows' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'deletes the allowed domain', :aggregate_failures do
subject
@@ -15,6 +15,7 @@ RSpec.describe 'Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when there are no domain blocks' do
it 'returns an empty list' do
@@ -89,6 +90,7 @@ RSpec.describe 'Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the expected domain block content', :aggregate_failures do
subject
@@ -131,6 +133,7 @@ RSpec.describe 'Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'creates a domain block with the expected domain name and severity', :aggregate_failures do
subject
@@ -239,6 +242,7 @@ RSpec.describe 'Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the updated domain block', :aggregate_failures do
subject
@@ -281,6 +285,7 @@ RSpec.describe 'Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'deletes the domain block', :aggregate_failures do
subject
@@ -15,6 +15,7 @@ RSpec.describe 'Email Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when there is no email domain block' do
it 'returns an empty list' do
@@ -90,6 +91,7 @@ RSpec.describe 'Email Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when email domain block exists' do
it 'returns the correct blocked domain', :aggregate_failures do
@@ -123,6 +125,7 @@ RSpec.describe 'Email Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the correct blocked email domain', :aggregate_failures do
subject
@@ -182,6 +185,7 @@ RSpec.describe 'Email Domain Blocks' do
it_behaves_like 'forbidden for wrong scope', 'read:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'deletes email domain block', :aggregate_failures do
subject
@@ -15,6 +15,7 @@ RSpec.describe 'IP Blocks' do
it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
context 'when there is no ip block' do
it 'returns an empty body' do
@@ -83,6 +84,7 @@ RSpec.describe 'IP Blocks' do
it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the correct ip block', :aggregate_failures do
subject
@@ -119,6 +121,7 @@ RSpec.describe 'IP Blocks' do
it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for wrong role', 'Moderator'
it_behaves_like 'forbidden for disabled user'
it 'returns the correct ip block', :aggregate_failures do
subject
@@ -14,6 +14,7 @@ RSpec.describe 'Reports' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
context 'when there are no reports' do
it 'returns an empty list' do
@@ -132,6 +133,7 @@ RSpec.describe 'Reports' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns the requested report content', :aggregate_failures do
subject
@@ -199,6 +201,7 @@ RSpec.describe 'Reports' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'marks report as resolved', :aggregate_failures do
expect { subject }
@@ -219,6 +222,7 @@ RSpec.describe 'Reports' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'marks report as unresolved', :aggregate_failures do
expect { subject }
@@ -239,6 +243,7 @@ RSpec.describe 'Reports' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'assigns report to the requesting user', :aggregate_failures do
expect { subject }
@@ -259,6 +264,7 @@ RSpec.describe 'Reports' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'unassigns report from assignee', :aggregate_failures do
expect { subject }
+3
View File
@@ -17,6 +17,7 @@ RSpec.describe 'Tags' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
context 'when there are no tags' do
it 'returns an empty list' do
@@ -74,6 +75,7 @@ RSpec.describe 'Tags' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success and expected tag content' do
subject
@@ -111,6 +113,7 @@ RSpec.describe 'Tags' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong scope', 'admin:read'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success and updates tag' do
subject
@@ -28,6 +28,7 @@ RSpec.describe 'Links' do
it_behaves_like 'forbidden for wrong scope', 'read write'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect { subject }
@@ -87,6 +88,7 @@ RSpec.describe 'Links' do
it_behaves_like 'forbidden for wrong scope', 'read write'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect { subject }
@@ -25,6 +25,7 @@ RSpec.describe 'API V1 Admin Trends Links Preview Card Providers' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -40,6 +41,7 @@ RSpec.describe 'API V1 Admin Trends Links Preview Card Providers' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -25,6 +25,7 @@ RSpec.describe 'API V1 Admin Trends Statuses' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -40,6 +41,7 @@ RSpec.describe 'API V1 Admin Trends Statuses' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -25,6 +25,7 @@ RSpec.describe 'API V1 Admin Trends Tags' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect(response).to have_http_status(200)
@@ -40,6 +41,7 @@ RSpec.describe 'API V1 Admin Trends Tags' do
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', ''
it_behaves_like 'forbidden for disabled user'
it 'returns http success' do
expect(response).to have_http_status(200)
+1 -1
View File
@@ -27,7 +27,7 @@ RSpec.describe 'Auth Passwords' do
expect(User.find(user.id))
.to be_present
.and be_external_or_valid_password(user.password)
.and be_valid_password(user.password)
end
end
end
+11
View File
@@ -22,3 +22,14 @@ RSpec.shared_examples 'forbidden for wrong role' do |wrong_role|
expect(response).to have_http_status(403)
end
end
RSpec.shared_examples 'forbidden for disabled user' do
let(:user) { Fabricate(:admin_user, disabled: true) }
it 'returns http forbidden' do
# Some examples have a subject which needs to be called to make a request
subject if request.nil?
expect(response).to have_http_status(403)
end
end
+114
View File
@@ -36,6 +36,120 @@ RSpec.describe 'Auth Registration' do
end
end
context 'with approval-based registrations' do
subject { visit new_user_registration_path }
shared_examples 'approval is required' do
context 'when reason to join is not required' do
before { Setting.require_invite_text = false }
it 'asks for an optional reason to join, creates the user as unapproved' do
subject
expect(page)
.to have_title(I18n.t('auth.register'))
expect(page)
.to have_text(I18n.t('simple_form.labels.invite_request.text'))
expect { fill_in_and_submit_form(apply: true) }
.to change(User, :count).by(1)
expect(User.find_by(email: 'test@example.com'))
.to have_attributes(approved: false)
end
end
context 'when reason to join is required' do
before { Setting.require_invite_text = true }
it 'asks for a required reason to join, creates the user unapproved' do
subject
expect(page)
.to have_title(I18n.t('auth.register'))
expect(page)
.to have_text(I18n.t('simple_form.labels.invite_request.text'))
# Not providing the invite text results in an error
expect { fill_in_and_submit_form(apply: true) }
.to_not change(User, :count)
expect(page)
.to have_text(/error below/)
# Providing the invite text succeeds
expect { fill_in_and_submit_form(apply: true, invite_text: 'Hello world') }
.to change(User, :count).by(1)
expect(User.find_by(email: 'test@example.com'))
.to have_attributes(approved: false)
end
end
end
before do
Setting.registrations_mode = 'approved'
end
it_behaves_like 'approval is required'
context 'with an invitation' do
subject { visit new_user_registration_path(invite_code: invite.code) }
let!(:inviter) { Fabricate(:user, confirmed_at: 2.days.ago, bypass_registration_checks: true) }
let(:invite) { Fabricate(:invite, user: inviter) }
before do
inviter.approve!
end
it_behaves_like 'approval is required'
context 'when the invite allows bypassing approval' do
before do
inviter.role.update!(permissions: inviter.role.permissions | UserRole::FLAGS[:invite_bypass_approval])
end
it 'does not ask for a required reason to join, creates the user approved' do
subject
expect(page)
.to have_title(I18n.t('auth.register'))
expect(page)
.to have_no_text(I18n.t('simple_form.labels.invite_request.text'))
expect { fill_in_and_submit_form(apply: false) }
.to change(User, :count).by(1)
expect(User.find_by(email: 'test@example.com'))
.to have_attributes(approved: true)
end
end
context 'when invite has expired' do
let(:invite) { Fabricate(:invite, user: inviter, expires_at: 1.hour.ago) }
it_behaves_like 'approval is required'
end
end
def fill_in_and_submit_form(apply: false, invite_text: nil)
# Avoid the registration spam check
travel_to 10.seconds.from_now
fill_in 'user_account_attributes_username', with: 'test'
fill_in 'user_email', with: 'test@example.com'
fill_in 'user_password', with: 'Test.123.Pass'
fill_in 'user_password_confirmation', with: 'Test.123.Pass'
fill_in 'user_invite_request_attributes_text', with: invite_text if invite_text.present?
check 'user_agreement'
click_on(apply ? I18n.t('auth.apply_for_account') : I18n.t('auth.register'))
end
end
context 'when age verification is enabled' do
before { Setting.min_age = 16 }