diff --git a/.codeclimate.yml b/.codeclimate.yml index 29701a7775..47e3e6ab9d 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,21 +1,36 @@ -engines: +version: "2" +checks: + argument-count: + enabled: false + complex-logic: + enabled: false + file-lines: + enabled: false + method-complexity: + enabled: false + method-count: + enabled: false + method-lines: + enabled: false + nested-control-flow: + enabled: false + return-statements: + enabled: false + similar-code: + enabled: false + identical-code: + enabled: false +plugins: brakeman: enabled: true bundler-audit: enabled: true - duplication: - enabled: false eslint: enabled: true rubocop: enabled: true scss-lint: enabled: true -ratings: - paths: - - "**.rb" - - "**.js" - - "**.scss" -exclude_paths: +exclude_patterns: - spec/ - vendor/asset diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7cec571806..4b20fe971b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eugen@zeonfederated.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at beatrix.bitrot@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/Gemfile.lock b/Gemfile.lock index f9c69d5386..37fc77fdf7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -24,11 +24,11 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_model_serializers (0.10.6) + active_model_serializers (0.10.7) actionpack (>= 4.1, < 6) activemodel (>= 4.1, < 6) case_transform (>= 0.2) - jsonapi-renderer (>= 0.1.1.beta1, < 0.2) + jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.5.4) activejob (5.1.4) activesupport (= 5.1.4) @@ -83,15 +83,15 @@ GEM capistrano-bundler (1.3.0) capistrano (~> 3.1) sshkit (~> 1.2) - capistrano-rails (1.3.0) + capistrano-rails (1.3.1) capistrano (~> 3.1) capistrano-bundler (~> 1.1) - capistrano-rbenv (2.1.2) + capistrano-rbenv (2.1.3) capistrano (~> 3.1) sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (2.15.4) + capybara (2.16.1) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) @@ -113,7 +113,7 @@ GEM connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.2) + crass (1.0.3) debug_inspector (0.0.3) devise (4.3.0) bcrypt (~> 3.0) @@ -121,11 +121,11 @@ GEM railties (>= 4.1.0, < 5.2) responders warden (~> 1.2.3) - devise-two-factor (3.0.0) - activesupport + devise-two-factor (3.0.2) + activesupport (< 5.2) attr_encrypted (>= 1.3, < 4, != 2) devise (~> 4.0) - railties + railties (< 5.2) rotp (~> 2.0) diff-lcs (1.3) docile (1.1.5) @@ -184,7 +184,7 @@ GEM http (~> 2.2) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.8.4) + hamlit (2.8.5) temple (>= 0.8.0) thor tilt @@ -196,7 +196,7 @@ GEM hamster (3.0.0) concurrent-ruby (~> 1.0) hashdiff (0.3.7) - highline (1.7.8) + highline (1.7.10) hiredis (0.6.1) hkdf (0.3.0) htmlentities (4.3.4) @@ -213,9 +213,9 @@ GEM httplog (0.99.7) colorize rack - i18n (0.9.0) + i18n (0.9.1) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.18) + i18n-tasks (0.9.19) activesupport (>= 4.0.2) ast (>= 2.1.0) easy_translate (>= 0.5.0) @@ -236,8 +236,8 @@ GEM json-ld (~> 2.1, >= 2.1.5) multi_json (~> 1.11) rdf (~> 2.2) - jsonapi-renderer (0.1.3) - jwt (1.5.6) + jsonapi-renderer (0.2.0) + jwt (2.1.0) kaminari (1.1.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.1.1) @@ -267,8 +267,8 @@ GEM loofah (2.1.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.6.6) - mime-types (>= 1.16, < 4) + mail (2.7.0) + mini_mime (>= 0.1.1) mario-redis-lock (1.2.0) redis (~> 3, >= 3.0.5) method_source (0.9.0) @@ -279,7 +279,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mimemagic (0.3.2) - mini_mime (0.1.4) + mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.10.3) msgpack (1.1.0) @@ -305,7 +305,7 @@ GEM http (~> 2.0) nokogiri (~> 1.6) openssl (~> 2.0) - ox (2.8.1) + ox (2.8.2) paperclip (5.1.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -316,22 +316,22 @@ GEM av (~> 0.9.0) paperclip (>= 2.5.2) parallel (1.12.0) - parallel_tests (2.17.0) + parallel_tests (2.19.0) parallel - parser (2.4.0.0) - ast (~> 2.2) + parser (2.4.0.2) + ast (~> 2.3) pg (0.21.0) pghero (1.7.0) activerecord pkg-config (1.2.8) powerpack (0.1.1) - pry (0.11.2) + pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) pry-rails (0.3.6) pry (>= 0.10.4) - public_suffix (3.0.0) - puma (3.10.0) + public_suffix (3.0.1) + puma (3.11.0) pundit (1.1.0) activesupport (>= 3.0.0) rabl (0.13.1) @@ -344,7 +344,7 @@ GEM rack rack-proxy (0.6.2) rack - rack-test (0.7.0) + rack-test (0.8.2) rack (>= 1.0, < 3) rack-timeout (0.4.2) rails (5.1.4) @@ -381,8 +381,11 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.2.2) rake - rake (12.2.1) - rdf (2.2.11) + rake (12.3.0) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rdf (2.2.12) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.2) @@ -395,8 +398,8 @@ GEM redis-activesupport (5.0.4) activesupport (>= 3, < 6) redis-store (>= 1.3, < 2) - redis-namespace (1.5.3) - redis (~> 3.0, >= 3.0.4) + redis-namespace (1.6.0) + redis (>= 3.0.4) redis-rack (2.0.3) rack (>= 1.5, < 3) redis-store (>= 1.2, < 2) @@ -421,7 +424,7 @@ GEM rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) - rspec-rails (3.7.1) + rspec-rails (3.7.2) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) @@ -449,10 +452,14 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.4.4) nokogumbo (~> 1.4.1) - sass (3.4.25) - scss_lint (0.55.0) + sass (3.5.3) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + scss_lint (0.56.0) rake (>= 0.9, < 13) - sass (~> 3.4.20) + sass (~> 3.5.3) sidekiq (5.0.5) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) @@ -486,7 +493,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.14.0) + sshkit (1.15.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) statsd-ruby (1.2.1) @@ -514,7 +521,7 @@ GEM uniform_notifier (1.10.0) warden (1.2.7) rack (>= 1.0) - webmock (3.1.0) + webmock (3.1.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff @@ -522,12 +529,12 @@ GEM activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) - webpush (0.3.2) + webpush (0.3.3) hkdf (~> 0.2) - jwt + jwt (~> 2.0) websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.2) + websocket-extensions (0.1.3) xpath (2.1.0) nokogiri (~> 1.3) @@ -638,4 +645,4 @@ RUBY VERSION ruby 2.4.2p198 BUNDLED WITH - 1.15.4 + 1.16.0 diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb index 7f69a33638..7d5b9bf52c 100644 --- a/app/controllers/admin/account_moderation_notes_controller.rb +++ b/app/controllers/admin/account_moderation_notes_controller.rb @@ -21,7 +21,7 @@ module Admin def destroy authorize @account_moderation_note, :destroy? - @account_moderation_note.destroy + @account_moderation_note.destroy! redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg') end diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index 0829bc769f..e9a512e70c 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -32,18 +32,21 @@ module Admin def memorialize authorize @account, :memorialize? @account.memorialize! + log_action :memorialize, @account redirect_to admin_account_path(@account.id) end def enable authorize @account.user, :enable? @account.user.enable! + log_action :enable, @account.user redirect_to admin_account_path(@account.id) end def disable authorize @account.user, :disable? @account.user.disable! + log_action :disable, @account.user redirect_to admin_account_path(@account.id) end diff --git a/app/controllers/admin/action_logs_controller.rb b/app/controllers/admin/action_logs_controller.rb new file mode 100644 index 0000000000..e273dfeaef --- /dev/null +++ b/app/controllers/admin/action_logs_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Admin + class ActionLogsController < BaseController + def index + @action_logs = Admin::ActionLog.page(params[:page]) + end + end +end diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 7261345090..fc299f74c5 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -3,6 +3,7 @@ module Admin class BaseController < ApplicationController include Authorization + include AccountableConcern layout 'admin' diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb index c10b0ebee8..34dfb458ec 100644 --- a/app/controllers/admin/confirmations_controller.rb +++ b/app/controllers/admin/confirmations_controller.rb @@ -7,6 +7,7 @@ module Admin def create authorize @user, :confirm? @user.confirm! + log_action :confirm, @user redirect_to admin_accounts_path end diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb index 509f7a48f7..3fa2a0b72e 100644 --- a/app/controllers/admin/custom_emojis_controller.rb +++ b/app/controllers/admin/custom_emojis_controller.rb @@ -20,6 +20,7 @@ module Admin @custom_emoji = CustomEmoji.new(resource_params) if @custom_emoji.save + log_action :create, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg') else render :new @@ -30,6 +31,7 @@ module Admin authorize @custom_emoji, :update? if @custom_emoji.update(resource_params) + log_action :update, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg') else redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg') @@ -38,7 +40,8 @@ module Admin def destroy authorize @custom_emoji, :destroy? - @custom_emoji.destroy + @custom_emoji.destroy! + log_action :destroy, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg') end @@ -49,6 +52,7 @@ module Admin emoji.image = @custom_emoji.image if emoji.save + log_action :create, emoji flash[:notice] = I18n.t('admin.custom_emojis.copied_msg') else flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg') @@ -60,12 +64,14 @@ module Admin def enable authorize @custom_emoji, :enable? @custom_emoji.update!(disabled: false) + log_action :enable, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg') end def disable authorize @custom_emoji, :disable? @custom_emoji.update!(disabled: true) + log_action :disable, @custom_emoji redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg') end diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index e383dc8314..64de2cbf0c 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -21,6 +21,7 @@ module Admin if @domain_block.save DomainBlockWorker.perform_async(@domain_block.id) + log_action :create, @domain_block redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.created_msg') else render :new @@ -34,6 +35,7 @@ module Admin def destroy authorize @domain_block, :destroy? UnblockDomainService.new.call(@domain_block, retroactive_unblock?) + log_action :destroy, @domain_block redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/email_domain_blocks_controller.rb b/app/controllers/admin/email_domain_blocks_controller.rb index 01058bf467..9fe85064e3 100644 --- a/app/controllers/admin/email_domain_blocks_controller.rb +++ b/app/controllers/admin/email_domain_blocks_controller.rb @@ -20,6 +20,7 @@ module Admin @email_domain_block = EmailDomainBlock.new(resource_params) if @email_domain_block.save + log_action :create, @email_domain_block redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg') else render :new @@ -28,7 +29,8 @@ module Admin def destroy authorize @email_domain_block, :destroy? - @email_domain_block.destroy + @email_domain_block.destroy! + log_action :destroy, @email_domain_block redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg') end diff --git a/app/controllers/admin/invites_controller.rb b/app/controllers/admin/invites_controller.rb new file mode 100644 index 0000000000..faccaa7c89 --- /dev/null +++ b/app/controllers/admin/invites_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Admin + class InvitesController < BaseController + def index + authorize :invite, :index? + + @invites = filtered_invites.includes(user: :account).page(params[:page]) + @invite = Invite.new + end + + def create + authorize :invite, :create? + + @invite = Invite.new(resource_params) + @invite.user = current_user + + if @invite.save + redirect_to admin_invites_path + else + @invites = Invite.page(params[:page]) + render :index + end + end + + def destroy + @invite = Invite.find(params[:id]) + authorize @invite, :destroy? + @invite.expire! + redirect_to admin_invites_path + end + + private + + def resource_params + params.require(:invite).permit(:max_uses, :expires_in) + end + + def filtered_invites + InviteFilter.new(filter_params).results + end + + def filter_params + params.permit(:available, :expired) + end + end +end diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 4f66ce708a..535bd11d48 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -8,7 +8,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) @@ -16,13 +16,15 @@ module Admin def update authorize @status, :update? - @status.update(status_params) + @status.update!(status_params) + log_action :update, @status redirect_to admin_report_path(@report) end def destroy authorize @status, :destroy? RemovalWorker.perform_async(@status.id) + log_action :destroy, @status render json: @status end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 745757ee82..75db6b78aa 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -25,12 +25,17 @@ module Admin def process_report case params[:outcome].to_s when 'resolve' - @report.update(action_taken_by_current_attributes) + @report.update!(action_taken_by_current_attributes) + log_action :resolve, @report when 'suspend' Admin::SuspensionWorker.perform_async(@report.target_account.id) + log_action :resolve, @report + log_action :suspend, @report.target_account resolve_all_target_account_reports when 'silence' - @report.target_account.update(silenced: true) + @report.target_account.update!(silenced: true) + log_action :resolve, @report + log_action :silence, @report.target_account resolve_all_target_account_reports else raise ActiveRecord::RecordNotFound diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb index 00b590bf67..3e27d01ac2 100644 --- a/app/controllers/admin/resets_controller.rb +++ b/app/controllers/admin/resets_controller.rb @@ -7,6 +7,7 @@ module Admin def create authorize @user, :reset_password? @user.send_reset_password_instructions + log_action :reset_password, @user redirect_to admin_accounts_path end diff --git a/app/controllers/admin/roles_controller.rb b/app/controllers/admin/roles_controller.rb index 8f86858279..af7ec0740d 100644 --- a/app/controllers/admin/roles_controller.rb +++ b/app/controllers/admin/roles_controller.rb @@ -7,12 +7,14 @@ module Admin def promote authorize @user, :promote? @user.promote! + log_action :promote, @user redirect_to admin_account_path(@user.account_id) end def demote authorize @user, :demote? @user.demote! + log_action :demote, @user redirect_to admin_account_path(@user.account_id) end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index e81290228e..eed5fb6b57 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -13,14 +13,17 @@ module Admin closed_registrations_message open_deletion timeline_preview + show_staff_badge bootstrap_timeline_accounts thumbnail + min_invite_role ).freeze BOOLEAN_SETTINGS = %w( open_registrations open_deletion timeline_preview + show_staff_badge ).freeze UPLOAD_SETTINGS = %w( diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb index 01fb292de0..4c06a9c0cc 100644 --- a/app/controllers/admin/silences_controller.rb +++ b/app/controllers/admin/silences_controller.rb @@ -6,13 +6,15 @@ module Admin def create authorize @account, :silence? - @account.update(silenced: true) + @account.update!(silenced: true) + log_action :silence, @account redirect_to admin_accounts_path end def destroy authorize @account, :unsilence? - @account.update(silenced: false) + @account.update!(silenced: false) + log_action :unsilence, @account redirect_to admin_accounts_path end diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb index b54a9b8247..5d4325f574 100644 --- a/app/controllers/admin/statuses_controller.rb +++ b/app/controllers/admin/statuses_controller.rb @@ -26,7 +26,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_account_statuses_path(@account.id, current_params) @@ -34,13 +34,15 @@ module Admin def update authorize @status, :update? - @status.update(status_params) + @status.update!(status_params) + log_action :update, @status redirect_to admin_account_statuses_path(@account.id, current_params) end def destroy authorize @status, :destroy? RemovalWorker.perform_async(@status.id) + log_action :destroy, @status render json: @status end diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb index 778feea5e8..5f222e1258 100644 --- a/app/controllers/admin/suspensions_controller.rb +++ b/app/controllers/admin/suspensions_controller.rb @@ -7,12 +7,14 @@ module Admin def create authorize @account, :suspend? Admin::SuspensionWorker.perform_async(@account.id) + log_action :suspend, @account redirect_to admin_accounts_path end def destroy authorize @account, :unsuspend? @account.unsuspend! + log_action :unsuspend, @account redirect_to admin_accounts_path end diff --git a/app/controllers/admin/two_factor_authentications_controller.rb b/app/controllers/admin/two_factor_authentications_controller.rb index 5a45d25cd0..0221072032 100644 --- a/app/controllers/admin/two_factor_authentications_controller.rb +++ b/app/controllers/admin/two_factor_authentications_controller.rb @@ -7,6 +7,7 @@ module Admin def destroy authorize @user, :disable_2fa? @user.disable_two_factor! + log_action :disable_2fa, @user redirect_to admin_accounts_path end diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 85eb2d60e2..b1a2ed5737 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -13,11 +13,9 @@ class Api::V1::AccountsController < Api::BaseController end def follow - reblogs_arg = { reblogs: params[:reblogs] } - - FollowService.new.call(current_user.account, @account.acct, reblogs_arg) + FollowService.new.call(current_user.account, @account.acct, reblogs: params[:reblogs]) - options = @account.locked? ? {} : { following_map: { @account.id => reblogs_arg }, requested_map: { @account.id => false } } + options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: params[:reblogs] } }, requested_map: { @account.id => false } } render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) end diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index 42e852c049..f4247fd95e 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -17,13 +17,16 @@ class Auth::RegistrationsController < Devise::RegistrationsController def build_resource(hash = nil) super(hash) - resource.locale = I18n.locale + + resource.locale = I18n.locale + resource.invite_code = params[:invite_code] if resource.invite_code.blank? + resource.build_account if resource.account.nil? end def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up) do |u| - u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation) + u.permit({ account_attributes: [:username] }, :email, :password, :password_confirmation, :invite_code) end end @@ -36,7 +39,19 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def check_enabled_registrations - redirect_to root_path if single_user_mode? || !Setting.open_registrations + redirect_to root_path if single_user_mode? || !allowed_registrations? + end + + def allowed_registrations? + Setting.open_registrations || (invite_code.present? && Invite.find_by(code: invite_code)&.valid_for_use?) + end + + def invite_code + if params[:user] + params[:user][:invite_code] + else + params[:invite_code] + end end private diff --git a/app/controllers/concerns/accountable_concern.rb b/app/controllers/concerns/accountable_concern.rb new file mode 100644 index 0000000000..3cdcffc51c --- /dev/null +++ b/app/controllers/concerns/accountable_concern.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module AccountableConcern + extend ActiveSupport::Concern + + def log_action(action, target) + Admin::ActionLog.create(account: current_account, action: action, target: target) + end +end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb new file mode 100644 index 0000000000..38d6c8d73e --- /dev/null +++ b/app/controllers/invites_controller.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class InvitesController < ApplicationController + include Authorization + + layout 'admin' + + before_action :authenticate_user! + + def index + authorize :invite, :create? + + @invites = Invite.where(user: current_user) + @invite = Invite.new(expires_in: 1.day.to_i) + end + + def create + authorize :invite, :create? + + @invite = Invite.new(resource_params) + @invite.user = current_user + + if @invite.save + redirect_to invites_path + else + @invites = Invite.where(user: current_user) + render :index + end + end + + def destroy + @invite = Invite.where(user: current_user).find(params[:id]) + authorize @invite, :destroy? + @invite.expire! + redirect_to invites_path + end + + private + + def resource_params + params.require(:invite).permit(:max_uses, :expires_in) + end +end diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb new file mode 100644 index 0000000000..b18403a7f0 --- /dev/null +++ b/app/controllers/settings/migrations_controller.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class Settings::MigrationsController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + + def show + @migration = Form::Migration.new(account: current_account.moved_to_account) + end + + def update + @migration = Form::Migration.new(resource_params) + + if @migration.valid? && migration_account_changed? + current_account.update!(moved_to_account: @migration.account) + ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) + redirect_to settings_migration_path, notice: I18n.t('migrations.updated_msg') + else + render :show + end + end + + private + + def resource_params + params.require(:migration).permit(:acct) + end + + def migration_account_changed? + current_account.moved_to_account_id != @migration.account&.id + end +end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb new file mode 100644 index 0000000000..e85243e57e --- /dev/null +++ b/app/helpers/admin/action_logs_helper.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +module Admin::ActionLogsHelper + def log_target(log) + if log.target + linkable_log_target(log.target) + else + log_target_from_history(log.target_type, log.recorded_changes) + end + end + + def linkable_log_target(record) + case record.class.name + when 'Account' + link_to record.acct, admin_account_path(record.id) + when 'User' + link_to record.account.acct, admin_account_path(record.account_id) + when 'CustomEmoji' + record.shortcode + when 'Report' + link_to "##{record.id}", admin_report_path(record) + when 'DomainBlock', 'EmailDomainBlock' + link_to record.domain, "https://#{record.domain}" + when 'Status' + link_to record.account.acct, TagManager.instance.url_for(record) + end + end + + def log_target_from_history(type, attributes) + case type + when 'CustomEmoji' + attributes['shortcode'] + when 'DomainBlock', 'EmailDomainBlock' + link_to attributes['domain'], "https://#{attributes['domain']}" + when 'Status' + tmp_status = Status.new(attributes) + link_to tmp_status.account.acct, TagManager.instance.url_for(tmp_status) + end + end + + def relevant_log_changes(log) + if log.target_type == 'CustomEmoji' && [:enable, :disable, :destroy].include?(log.action) + log.recorded_changes.slice('domain') + elsif log.target_type == 'CustomEmoji' && log.action == :update + log.recorded_changes.slice('domain', 'visible_in_picker') + elsif log.target_type == 'User' && [:promote, :demote].include?(log.action) + log.recorded_changes.slice('moderator', 'admin') + elsif log.target_type == 'DomainBlock' + log.recorded_changes.slice('severity', 'reject_media') + elsif log.target_type == 'Status' && log.action == :update + log.recorded_changes.slice('sensitive') + end + end + + def log_extra_attributes(hash) + safe_join(hash.to_a.map { |key, value| safe_join([content_tag(:span, key, class: 'diff-key'), '=', log_change(value)]) }, ' ') + end + + def log_change(val) + return content_tag(:span, val, class: 'diff-neutral') unless val.is_a?(Array) + safe_join([content_tag(:span, val.first, class: 'diff-old'), content_tag(:span, val.last, class: 'diff-new')], '→') + end + + def icon_for_log(log) + case log.target_type + when 'Account', 'User' + 'user' + when 'CustomEmoji' + 'file' + when 'Report' + 'flag' + when 'DomainBlock' + 'lock' + when 'EmailDomainBlock' + 'envelope' + when 'Status' + 'pencil' + end + end + + def class_for_log_icon(log) + case log.action + when :enable, :unsuspend, :unsilence, :confirm, :promote, :resolve + 'positive' + when :create + opposite_verbs?(log) ? 'negative' : 'positive' + when :update, :reset_password, :disable_2fa, :memorialize + 'neutral' + when :demote, :silence, :disable, :suspend + 'negative' + when :destroy + opposite_verbs?(log) ? 'positive' : 'negative' + else + '' + end + end + + private + + def opposite_verbs?(log) + %w(DomainBlock EmailDomainBlock).include?(log.target_type) + end +end diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index e0fae9d9a2..73250cbf56 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -3,8 +3,9 @@ module Admin::FilterHelper ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip).freeze REPORT_FILTERS = %i(resolved account_id target_account_id).freeze + INVITE_FILTER = %i(available expired).freeze - FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER def filter_link_to(text, link_to_params, link_class_params = link_to_params) new_url = filtered_url_for(link_to_params) diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index a3441e6f93..6c7c38070a 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -9,6 +9,24 @@ module JsonLdHelper value.is_a?(Array) ? value.first : value end + # The url attribute can be a string, an array of strings, or an array of objects. + # The objects could include a mimeType. Not-included mimeType means it's text/html. + def url_to_href(value, preferred_type = nil) + single_value = if value.is_a?(Array) && !value.first.is_a?(String) + value.find { |link| preferred_type.nil? || ((link['mimeType'].presence || 'text/html') == preferred_type) } + elsif value.is_a?(Array) + value.first + else + value + end + + if single_value.nil? || single_value.is_a?(String) + single_value + else + single_value['href'] + end + end + def as_array(value) value.is_a?(Array) ? value : [value] end diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 6662285d0a..b0d9e37576 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { MediaGallery, Video } from 'flavours/glitch/util/async-components'; import { HotKeys } from 'react-hotkeys'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; +import classNames from 'classnames'; // We use the component (and not the container) since we do not want // to use the progress bar to show download progress @@ -21,6 +22,7 @@ export default class Status extends ImmutablePureComponent { }; static propTypes = { + containerId: PropTypes.string, id: PropTypes.string, status: ImmutablePropTypes.map, account: ImmutablePropTypes.map, @@ -59,6 +61,7 @@ export default class Status extends ImmutablePureComponent { 'muted', 'collapse', 'notification', + 'hidden', ] updateOnStates = [ @@ -187,7 +190,9 @@ export default class Status extends ImmutablePureComponent { } handleExpandedToggle = () => { - this.setExpansion(this.state.isExpanded || !this.props.status.get('spoiler') ? null : true); + if (this.props.status.get('spoiler_text')) { + this.setExpansion(this.state.isExpanded ? null : true); + } }; handleOpenVideo = startTime => { @@ -221,11 +226,11 @@ export default class Status extends ImmutablePureComponent { } handleHotkeyMoveUp = () => { - this.props.onMoveUp(this.props.status.get('id')); + this.props.onMoveUp(this.props.containerId || this.props.id); } handleHotkeyMoveDown = () => { - this.props.onMoveDown(this.props.status.get('id')); + this.props.onMoveDown(this.props.containerId || this.props.id); } handleRef = c => { @@ -370,31 +375,24 @@ export default class Status extends ImmutablePureComponent { openProfile: this.handleHotkeyOpenProfile, moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, + toggleSpoiler: this.handleExpandedToggle, }; + const computedClass = classNames('status', `status-${status.get('visibility')}`, { + collapsed: isExpanded === false, + 'has-background': isExpanded === false && background, + 'marked-for-delete': this.state.markedForDelete, + muted, + }, 'focusable'); + return (
{prepend && account ? ( { } return { + containerId : props.containerId || props.id, // Should match reblogStatus's id for reblogs status : status, account : account || props.account, settings : state.get('local_settings'), diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js index 4b682733ee..54506f67c4 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow.js +++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js @@ -14,6 +14,7 @@ import NotificationOverlayContainer from '../containers/overlay_container'; export default class NotificationFollow extends ImmutablePureComponent { static propTypes = { + hidden: PropTypes.bool, id: PropTypes.string.isRequired, account: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired, @@ -57,7 +58,7 @@ export default class NotificationFollow extends ImmutablePureComponent { } render () { - const { account, notification } = this.props; + const { account, notification, hidden } = this.props; // Links to the display name. const displayName = account.get('display_name_html') || account.get('username'); @@ -87,7 +88,7 @@ export default class NotificationFollow extends ImmutablePureComponent { />
- +
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js index 185da8395a..cc77426d3b 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.js +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js @@ -16,70 +16,75 @@ export default class Notification extends ImmutablePureComponent { onMoveUp: PropTypes.func.isRequired, onMoveDown: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, - settings: ImmutablePropTypes.map.isRequired, }; - renderFollow () { - const { notification } = this.props; - return ( - - ); - } - - renderMention () { - const { notification } = this.props; - return ( - - ); - } - - renderFavourite () { - const { notification } = this.props; - return ( - - ); - } - - renderReblog () { - const { notification } = this.props; - return ( - - ); - } - render () { - const { notification } = this.props; + const { + hidden, + notification, + onMoveDown, + onMoveUp, + onMention, + } = this.props; + switch(notification.get('type')) { case 'follow': - return this.renderFollow(); + return ( +