diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e97984caf..7ea2ea3a79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Gemfile.lock b/Gemfile.lock index 100e11d47e..694af87fb0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 14338dd293..6338c29754 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/account_actions_controller.rb b/app/controllers/api/v1/admin/account_actions_controller.rb index 7249797a40..fd2b91c125 100644 --- a/app/controllers/api/v1/admin/account_actions_controller.rb +++ b/app/controllers/api/v1/admin/account_actions_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb index ff6f41e01d..cc42db2535 100644 --- a/app/controllers/api/v1/admin/accounts_controller.rb +++ b/app/controllers/api/v1/admin/accounts_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 diff --git a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb index c144a9e0f9..12c2603087 100644 --- a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb +++ b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 diff --git a/app/controllers/api/v1/admin/dimensions_controller.rb b/app/controllers/api/v1/admin/dimensions_controller.rb index 4a72ad08be..1f8dc9f2aa 100644 --- a/app/controllers/api/v1/admin/dimensions_controller.rb +++ b/app/controllers/api/v1/admin/dimensions_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb index 24f68aa1bd..e3f5ddb7ab 100644 --- a/app/controllers/api/v1/admin/domain_allows_controller.rb +++ b/app/controllers/api/v1/admin/domain_allows_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 MAX_LIMIT = 500 diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb index b44ae2ae2a..90dec85c15 100644 --- a/app/controllers/api/v1/admin/domain_blocks_controller.rb +++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 MAX_LIMIT = 500 diff --git a/app/controllers/api/v1/admin/email_domain_blocks_controller.rb b/app/controllers/api/v1/admin/email_domain_blocks_controller.rb index e7bd804e36..571d5f6725 100644 --- a/app/controllers/api/v1/admin/email_domain_blocks_controller.rb +++ b/app/controllers/api/v1/admin/email_domain_blocks_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 diff --git a/app/controllers/api/v1/admin/ip_blocks_controller.rb b/app/controllers/api/v1/admin/ip_blocks_controller.rb index e132a3a87d..e3671ea494 100644 --- a/app/controllers/api/v1/admin/ip_blocks_controller.rb +++ b/app/controllers/api/v1/admin/ip_blocks_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 diff --git a/app/controllers/api/v1/admin/measures_controller.rb b/app/controllers/api/v1/admin/measures_controller.rb index e90e50f999..8518cd5e41 100644 --- a/app/controllers/api/v1/admin/measures_controller.rb +++ b/app/controllers/api/v1/admin/measures_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb index 765996eb3c..1c72f844de 100644 --- a/app/controllers/api/v1/admin/reports_controller.rb +++ b/app/controllers/api/v1/admin/reports_controller.rb @@ -3,6 +3,7 @@ class Api::V1::Admin::ReportsController < Api::BaseController include Authorization include AccountableConcern + include Admin::PermissionsConcern LIMIT = 100 diff --git a/app/controllers/api/v1/admin/retention_controller.rb b/app/controllers/api/v1/admin/retention_controller.rb index 41f1be7edb..4acfbb6da9 100644 --- a/app/controllers/api/v1/admin/retention_controller.rb +++ b/app/controllers/api/v1/admin/retention_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/tags_controller.rb b/app/controllers/api/v1/admin/tags_controller.rb index dd272120e2..c57c771864 100644 --- a/app/controllers/api/v1/admin/tags_controller.rb +++ b/app/controllers/api/v1/admin/tags_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb index 2b0f39b98f..65e96924aa 100644 --- a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb +++ b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController include Authorization + include Admin::PermissionsConcern LIMIT = 100 diff --git a/app/controllers/api/v1/admin/trends/links_controller.rb b/app/controllers/api/v1/admin/trends/links_controller.rb index 7f4ca48288..13d7ae70c0 100644 --- a/app/controllers/api/v1/admin/trends/links_controller.rb +++ b/app/controllers/api/v1/admin/trends/links_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/trends/statuses_controller.rb b/app/controllers/api/v1/admin/trends/statuses_controller.rb index 34b6580df1..dfb1d70a2a 100644 --- a/app/controllers/api/v1/admin/trends/statuses_controller.rb +++ b/app/controllers/api/v1/admin/trends/statuses_controller.rb @@ -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 diff --git a/app/controllers/api/v1/admin/trends/tags_controller.rb b/app/controllers/api/v1/admin/trends/tags_controller.rb index 2eeea95225..d368ff8eaa 100644 --- a/app/controllers/api/v1/admin/trends/tags_controller.rb +++ b/app/controllers/api/v1/admin/trends/tags_controller.rb @@ -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 diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index 67bee2344e..3d757be679 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -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 diff --git a/app/controllers/concerns/admin/permissions_concern.rb b/app/controllers/concerns/admin/permissions_concern.rb new file mode 100644 index 0000000000..a38156619e --- /dev/null +++ b/app/controllers/concerns/admin/permissions_concern.rb @@ -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 diff --git a/app/controllers/concerns/auth/two_factor_authentication_concern.rb b/app/controllers/concerns/auth/two_factor_authentication_concern.rb index 0fb11428dc..5dca3be67e 100644 --- a/app/controllers/concerns/auth/two_factor_authentication_concern.rb +++ b/app/controllers/concerns/auth/two_factor_authentication_concern.rb @@ -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? diff --git a/app/helpers/json_ld_helper.rb b/app/helpers/json_ld_helper.rb index 03809a166c..ad765e7e1a 100644 --- a/app/helpers/json_ld_helper.rb +++ b/app/helpers/json_ld_helper.rb @@ -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) } diff --git a/app/models/user.rb b/app/models/user.rb index 7399afcb3a..602f4230e5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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 diff --git a/app/policies/admin/base_policy.rb b/app/policies/admin/base_policy.rb new file mode 100644 index 0000000000..77a458c0a6 --- /dev/null +++ b/app/policies/admin/base_policy.rb @@ -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 diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index 163b81e9e6..4be443bcd7 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -19,6 +19,8 @@ class ApplicationPolicy end def role + return UserRole.nobody if current_user&.disabled? + current_user&.role || UserRole.nobody end end diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb index 4a25b583db..008c6fee75 100644 --- a/app/services/activitypub/process_collection_service.rb +++ b/app/services/activitypub/process_collection_service.rb @@ -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') diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index 15e1bed973..b416d273cf 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index c160772564..af11913518 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/lib/exceptions.rb b/lib/exceptions.rb index 18a99ace2a..f938306cb1 100644 --- a/lib/exceptions.rb +++ b/lib/exceptions.rb @@ -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 diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 4fa66f6b54..4ae2e8bd04 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -13,7 +13,7 @@ module Mastodon end def patch - 6 + 7 end def default_prerelease diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index 6531608a01..c9e27f712e 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -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' diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 927c6ca8de..bd25d926f4 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -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 diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 35f0b98761..2b5021176f 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -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 } diff --git a/spec/policies/admin/base_policy_spec.rb b/spec/policies/admin/base_policy_spec.rb new file mode 100644 index 0000000000..3cd89d38be --- /dev/null +++ b/spec/policies/admin/base_policy_spec.rb @@ -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 diff --git a/spec/policies/application_policy_spec.rb b/spec/policies/application_policy_spec.rb new file mode 100644 index 0000000000..265be52177 --- /dev/null +++ b/spec/policies/application_policy_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb index c1273f468d..97549c8ed5 100644 --- a/spec/requests/api/v1/admin/account_actions_spec.rb +++ b/spec/requests/api/v1/admin/account_actions_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/accounts_spec.rb b/spec/requests/api/v1/admin/accounts_spec.rb index d94fcd31e4..0580e29679 100644 --- a/spec/requests/api/v1/admin/accounts_spec.rb +++ b/spec/requests/api/v1/admin/accounts_spec.rb @@ -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) diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb index b3af0a923d..513adb0891 100644 --- a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb index f3ae4076db..83b2ff4229 100644 --- a/spec/requests/api/v1/admin/domain_allows_spec.rb +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb index d532b0d25f..67753ece89 100644 --- a/spec/requests/api/v1/admin/domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb index a75b5abde4..d70d87f314 100644 --- a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index a9d6275294..368768549b 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb index 432e7f47a2..19dc4d6a5b 100644 --- a/spec/requests/api/v1/admin/reports_spec.rb +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -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 } diff --git a/spec/requests/api/v1/admin/tags_spec.rb b/spec/requests/api/v1/admin/tags_spec.rb index c84536d1b5..13de29f22b 100644 --- a/spec/requests/api/v1/admin/tags_spec.rb +++ b/spec/requests/api/v1/admin/tags_spec.rb @@ -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 diff --git a/spec/requests/api/v1/admin/trends/links/links_spec.rb b/spec/requests/api/v1/admin/trends/links/links_spec.rb index e49c9a0709..f3019811ae 100644 --- a/spec/requests/api/v1/admin/trends/links/links_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/links_spec.rb @@ -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 } diff --git a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb index 5fe6cac47f..6606a0c0ca 100644 --- a/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/requests/api/v1/admin/trends/links/preview_card_providers_spec.rb @@ -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) diff --git a/spec/requests/api/v1/admin/trends/statuses_spec.rb b/spec/requests/api/v1/admin/trends/statuses_spec.rb index 90a3d80a2a..f92f4831a7 100644 --- a/spec/requests/api/v1/admin/trends/statuses_spec.rb +++ b/spec/requests/api/v1/admin/trends/statuses_spec.rb @@ -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) diff --git a/spec/requests/api/v1/admin/trends/tags_spec.rb b/spec/requests/api/v1/admin/trends/tags_spec.rb index 750ee8975d..c96319a9b8 100644 --- a/spec/requests/api/v1/admin/trends/tags_spec.rb +++ b/spec/requests/api/v1/admin/trends/tags_spec.rb @@ -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) diff --git a/spec/requests/auth/passwords_spec.rb b/spec/requests/auth/passwords_spec.rb index feefd94587..6da0f6d05d 100644 --- a/spec/requests/auth/passwords_spec.rb +++ b/spec/requests/auth/passwords_spec.rb @@ -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 diff --git a/spec/support/examples/api.rb b/spec/support/examples/api.rb index 350166b10d..33de6242a6 100644 --- a/spec/support/examples/api.rb +++ b/spec/support/examples/api.rb @@ -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 diff --git a/spec/system/auth/registrations_spec.rb b/spec/system/auth/registrations_spec.rb index 2cf0dfe46f..acc4d89975 100644 --- a/spec/system/auth/registrations_spec.rb +++ b/spec/system/auth/registrations_spec.rb @@ -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 }